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

为 Kubernetes 服务提供强大、简单的 SSL

大家好,我是 Evan Brown (@evandbrown),我在 Google Cloud Platform 的解决方案架构团队工作。我最近写了一篇文章和一个教程,关于如何在 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 安装中未配置身份验证。在您连接并配置身份验证之前,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 有一个API 和 Secret 资源。Secret“旨在保存敏感信息,例如密码、OAuth 令牌和 SSH 密钥。将这些信息放入 Secret 中比直接放入 Pod 定义或 Docker 镜像中更安全、更灵活。”

您可以通过 3 个简单步骤在集群中创建 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..."

创建秘密资源

$ kubectl create -f secrets.json

要从容器访问 Secret,请在 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 模板,以便您可以自行构建镜像)。