本文发表已超过一年。较旧的文章可能包含过时的内容。请检查页面中的信息自发布以来是否已不正确。
用于 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 模板,因此你可以自己构建镜像)。