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

Kubernetes Services 的强大、简单 SSL

大家好,我是 Evan Brown (@evandbrown),我在 Google Cloud Platform 的解决方案架构团队工作。我最近写了一篇文章和一个教程,关于如何在 Kubernetes 上使用 Jenkins 实现 Docker 和 GCE 镜像构建过程的自动化。今天我将讨论如何使用 Kubernetes services 和 secrets 为 Jenkins web UI 添加 SSL。阅读本文后,您将能够为您的公共 HTTP Kubernetes services 添加 SSL 终止(以及 HTTP->HTTPS 重定向 + 基本认证)。

起初

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

  • Jenkins leader 只是一个 pod 中的一个容器,但它由一个 replication controller 管理,所以如果它失败了,它会自动重启。
  • Jenkins leader 暴露两个端口 - 用于 web UI 的 TCP 8080 和用于构建代理注册的 TCP 50000 - 这些端口通过带公共负载均衡器的 Kubernetes service 可用。

这是第一个版本的示意图

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

做你熟悉的事情

我曾短暂地考虑过直接在 Jenkins 上尝试实现 SSL。我以前从未做过,我不禁想知道它是否会像在Nginx 上使用 SSL 那样简单直接,这是我经验丰富的领域。我完全支持学习新事物,但这似乎是一个不重复造轮子的好地方:在 Nginx 上使用 SSL 既简单又文档齐全(其反向代理功能也是如此),而 Kubernetes 的核心理念就是通过编排和组合容器来构建功能。让我们使用 Nginx,并添加一些 Nginx 使其变得简单的额外功能:HTTP->HTTPS 重定向和基本访问认证。

将 SSL 终止代理作为 Nginx 服务

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

最后,start.sh 评估 SERVICE_HOST_ENV_NAME 和 SERVICE_PORT_ENV_NAME 环境变量。这些变量应该设置为您想要代理到的 Kubernetes service 对应的环境变量名称。在本例中,我们 Jenkins leader 的 service 被巧妙地命名为 jenkins,这意味着集群中的 pods 将看到名为 JENKINS_SERVICE_HOST 和 JENKINS_SERVICE_PORT_UI 的环境变量(8080 端口映射到 Jenkins leader 上的这个端口)。SERVICE_HOST_ENV_NAME 和 SERVICE_PORT_ENV_NAME 只是简单地引用了特定场景下要使用的正确 service,从而允许该镜像在不同的部署中通用。

定义 Controller 和 Service

就像本例中的其他所有 pod 一样,我们将使用 replication controller 部署 Nginx,从而允许我们横向扩缩容,并从容器故障中自动恢复。以下摘自示例应用中的完整描述文件,展示了 pod spec 的一些相关部分

  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 将拥有一个 service,通过公共负载均衡器暴露 TCP 80 和 443 端口。这是该 service 的描述文件 (也在示例应用中提供

  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 pods 是如何获得超秘密的 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 spec 中将其指定为卷挂载。以下是我们在前面看到的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"

定义了一个类型为 secret 的卷,它指向 ssl-proxy-secret Secret 资源,然后将其挂载到容器中的 /etc/secrets。前面示例中的 secrets spec 定义了 data.proxycert、data.proxykey 和 data.htpasswd,因此 Nginx 进程可以在 /etc/secrets/proxycert、/etc/secrets/proxykey 和 /etc/secrets/htpasswd 中看到这些文件(Base64 解码后)。

现在,让我们把这一切整合起来

我总是会经历“容器和 Kubernetes 真有趣真酷!”的时刻,可能每天都有。我开始更频繁地体验到“容器和 Kubernetes 非常有用和强大,通过帮助我轻松完成重要的事情,它们正在为我的工作增加价值。”这个使用 Nginx 的 SSL 终止代理示例绝对属于后者。我不需要浪费时间去学习一种新的使用 SSL 的方法。我能够使用熟悉的工具,以可重用的方式,快速地解决我的问题(从想到解决方案到实现工作只花了大约 2 小时)。

查看完整的使用 Jenkins、Packer 和 Kubernetes 进行自动化镜像构建仓库,了解 SSL 终止代理在实际集群中的使用方式,或者深入研究nginx-ssl-proxy 仓库中的代理镜像细节(包含 Dockerfile 和 Packer 模板,方便您自行构建镜像)。