本文发表已超过一年。较旧的文章可能包含过时的内容。请检查页面中的信息自发布以来是否已不正确。

用于 Kubernetes 服务的强大、简单 SSL

大家好,我是 Evan Brown (@evandbrown),我负责谷歌云平台的解决方案架构团队。我最近写了一篇关于使用 Kubernetes 上的 Jenkins 来自动化 Docker 和 GCE 镜像构建过程的文章教程。今天我将讨论如何使用 Kubernetes 服务和密钥来为 Jenkins Web UI 添加 SSL。阅读本文后,你将能够为你的公共 HTTP Kubernetes 服务添加 SSL 终止(以及 HTTP->HTTPS 重定向 + 基本身份验证)。

开始

本着最低可行性的精神,我构建的第一个 Jenkins-on-Kubernetes 版本非常基本但功能齐全。

  • Jenkins Leader 只是一个 pod 中的单个容器,但它由一个复制控制器管理,因此如果它失败,它会自动重新生成。
  • Jenkins Leader 公开两个端口 - TCP 8080 用于 Web UI,TCP 50000 用于构建代理注册 - 这些端口作为具有公共负载均衡器的 Kubernetes 服务提供。

这是第一个版本的可视化效果

这可以工作,但我对此有一些问题。首先,默认的 Jenkins 安装中未配置身份验证。Leader 位于公共互联网上,任何人都可以访问,直到你连接并配置身份验证。并且由于没有加密,配置身份验证只是一种象征性的姿态。我们需要 SSL,而且我们现在就需要!

做你所知道的事情

几毫秒内,我考虑尝试直接在 Jenkins 上使用 SSL。我以前从未这样做过,我发现自己想知道它是否像在 Nginx 上使用 SSL 一样简单,而我对 Nginx 有使用经验。我全力支持学习新事物,但这似乎是一个不重复造轮子的好地方:Nginx 上的 SSL 很简单且有充分的文档记录(其反向代理功能也是如此),而 Kubernetes 的全部意义在于通过编排和组合容器来构建功能。让我们使用 Nginx,并添加 Nginx 使其简单的几个额外功能:HTTP->HTTPS 重定向和基本访问身份验证。

作为 Nginx 服务的 SSL 终止代理

我首先编写了一个 Dockerfile,该文件继承自标准的 Nginx 镜像,复制了一些 Nginx 配置文件,并添加了一个自定义的入口点(start.sh)。入口点脚本检查一个环境变量 (ENABLE_SSL) 并相应地激活正确的 Nginx 配置(这意味着可以进行未加密的 HTTP 反向代理,但这违背了目的)。如果启用了基本访问身份验证,该脚本还会配置基本访问身份验证(ENABLE_BASIC_AUTH 环境变量)。

最后,start.sh 会评估 SERVICE_HOST_ENV_NAME 和 SERVICE_PORT_ENV_NAME 环境变量。这些变量应该设置为你要代理到的 Kubernetes 服务的环境变量名称。在此示例中,我们 Jenkins Leader 的服务被巧妙地命名为 jenkins,这意味着集群中的 pod 将看到一个名为 JENKINS_SERVICE_HOST 和 JENKINS_SERVICE_PORT_UI 的环境变量(端口 8080 映射到 Jenkins Leader 上的端口)。SERVICE_HOST_ENV_NAME 和 SERVICE_PORT_ENV_NAME 只是引用特定场景要使用的正确服务,允许该镜像在部署中通用。

定义控制器和服务

与此示例中的其他每个 pod 一样,我们将使用复制控制器部署 Nginx,从而允许我们向外或向内扩展,并自动从容器故障中恢复。这是 示例应用程序中的完整描述符中的一些相关 pod 规范片段

  spec:

    containers:

      -

        name: "nginx-ssl-proxy"

        image: "gcr.io/cloud-solutions-images/nginx-ssl-proxy:latest"

        env:

          -

            name: "SERVICE\_HOST\_ENV\_NAME"

            value: "JENKINS\_SERVICE\_HOST"

          -

            name: "SERVICE\_PORT\_ENV\_NAME"

            value: "JENKINS\_SERVICE\_PORT\_UI"

          -

            name: "ENABLE\_SSL"

            value: "true"

          -

            name: "ENABLE\_BASIC\_AUTH"

            value: "true"

        ports:

          -

            name: "nginx-ssl-proxy-http"

            containerPort: 80

          -

            name: "nginx-ssl-proxy-https"

            containerPort: 443

该 pod 将具有一个服务,该服务将 TCP 80 和 443 公开给公共负载均衡器。这是服务描述符 (也可在示例应用程序中找到)

  kind: "Service"

  apiVersion: "v1"

  metadata:

    name: "nginx-ssl-proxy"

    labels:

      name: "nginx"

      role: "ssl-proxy"

  spec:

    ports:

      -

        name: "https"

        port: 443

        targetPort: "nginx-ssl-proxy-https"

        protocol: "TCP"

      -

        name: "http"

        port: 80

        targetPort: "nginx-ssl-proxy-http"

        protocol: "TCP"

    selector:

      name: "nginx"

      role: "ssl-proxy"

    type: "LoadBalancer"

这是一个带有 SSL 终止代理的概述。请注意,Jenkins 不再直接暴露于公共互联网

现在,Nginx pod 如何获得超级机密的 SSL 密钥/证书和 htpasswd 文件(用于基本访问身份验证)?

保密,保持安全

Kubernetes 有一个用于 Secrets 的 API 和资源。Secrets“旨在保存敏感信息,例如密码、OAuth 令牌和 ssh 密钥。将此信息放入 Secret 中比将其逐字放入 pod 定义或 docker 镜像中更安全、更灵活。”

你可以在 3 个简单步骤中在集群中创建 secrets

对你的 secret 数据进行 Base64 编码(例如,SSL 密钥对或 htpasswd 文件)

$ cat ssl.key | base64  
   LS0tLS1CRUdJTiBDRVJUS...

创建一个描述你的 secret 的 json 文档,并添加 Base64 编码的值

  apiVersion: "v1"

  kind: "Secret"

  metadata:

    name: "ssl-proxy-secret"

    namespace: "default"

  data:

    proxycert: "LS0tLS1CRUd..."

    proxykey: "LS0tLS1CR..."

    htpasswd: "ZXZhb..."

创建 secrets 资源

$ kubectl create -f secrets.json

要从容器访问 secrets,请在 pod 规范中将它们指定为卷挂载。这是我们之前看到的 Nginx 代理模板中的相关片段

  spec:

    containers:

      -

        name: "nginx-ssl-proxy"

        image: "gcr.io/cloud-solutions-images/nginx-ssl-proxy:latest"

        env: [...]

        ports: ...[]

        volumeMounts:

          -

            name: "secrets"

            mountPath: "/etc/secrets"

            readOnly: true

    volumes:

      -

        name: "secrets"

        secret:

          secretName: "ssl-proxy-secret"

定义了一个指向 ssl-proxy-secret secret 资源的 secret 类型的卷,然后将其挂载到容器中的 /etc/secrets 中。前面示例中的 secrets 规范定义了 data.proxycert、data.proxykey 和 data.htpasswd,因此我们将看到这些文件(Base64 解码)出现在 /etc/secrets/proxycert、/etc/secrets/proxykey 和 /etc/secrets/htpasswd 中,供 Nginx 进程访问。

一起行动

我一直都有“容器和 Kubernetes 很有趣而且很酷!”的时刻,可能每天都有。我开始更频繁地拥有“容器和 Kubernetes 非常有用且强大,并且通过帮助我轻松完成重要的事情来为我的工作增加价值”的时刻。这个使用 Nginx 的 SSL 终止代理示例绝对是后者之一。我没有浪费时间学习使用 SSL 的新方法。我能够以可重用的方式使用众所周知的工具快速解决我的问题(从想法到工作大约需要 2 个小时)。

查看完整的 使用 Jenkins、Packer 和 Kubernetes 的自动化镜像构建存储库,了解如何在真实集群中使用 SSL 终止代理,或深入研究 nginx-ssl-proxy 存储库中的代理镜像的详细信息(包含 Dockerfile 和 Packer 模板,因此你可以自己构建镜像)。