容器-12-Kubernetes实战-静态网站部署优化1:ConfigMap,Secret与TLS

虽然我们已经成功地将一个静态网站成功地在Kubernetes里部署起来了,但还有很多细节可以完善。我们就在这一节里逐步优化。

1. ConfigMap

问题最明显的是。重温一下我们静态网站之前使用的Dockerfile:

1
2
3
FROM nginx:alpine
COPY default.conf /etc/nginx/conf.d/default.conf
COPY ./dist /usr/share/nginx/html

首先是Nginx配置default.conf。
网站的源代码不应该干涉网站怎么部署。到底部署在Apache,Nginx还是Node.js,是否要在部署的时候添加自定义Header,都不该是开发者关注的事情。我们也不希望修改网站的timeout配置还需要动到源代码。从耦合性的角度来看,这个Nginx网站的配置文件不应该放到源代码中。
对于这类配置文件,Kubernetes里有专门的对象ConfigMap来保存。

从ConfigMap这个名字就可以猜得到,它存储的是配置信息,存储的格式是Map类型,即键值对。
配置信息可以是像本篇中的Nginx config配置,可以设置环境变量,可以是Java的properties和application.yml配置文件,可以是Redis和MySQL的配置文件。它很适合需要在一套Kubernetes集群上部署多个环境(例如特性分支/sit/uat)的情况。(当然我们的Java应用将使用Spring Cloud Config配置中心,所以目前不会用ConfigMap管理配置)
本篇POC的ConfigMap如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
apiVersion: v1
kind: ConfigMap
metadata:
name: poc-web-config
labels:
app: poc-web
data:
default.conf: |
server {
listen 80;
server_name localhost;

charset utf-8;
#access_log /var/log/nginx/log/host.access.log main;

location / {
root /usr/share/nginx/html;
index index.html index.htm;
}

#error_page 404 /404.html;

# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}

default.conf为key(键),下面的内容为value(值)。结构非常简单。
在Kubernetes中,ConfigMap是一种特殊的Volume(卷):Projected Volume。可以认为ConfigMap是Kubernetes中的数据被投射(Project)到容器中的。
关于Volume我们会在后续展开讨论,这里只是先提一下:要在容器中使用volume,需要先在spec中定义,然后mount到容器中。所以添加了ConfigMap后的Deployment定义YAML如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
apiVersion: apps/v1
kind: Deployment
metadata:
name: poc-web
labels:
app: poc-web
spec:
replicas: 2
selector:
matchLabels:
app: poc-web
template:
metadata:
labels:
app: poc-web
spec:
containers:
- name: poc-web
image: 10.16.34.197:5000/staticsite:latest
ports:
- containerPort: 80
name: web
volumeMounts:
- name: poc-web-config
mountPath: /etc/nginx/conf.d
volumes:
- name: poc-web-config
configMap:
name: poc-web-config

poc-web-config中的default.conf被挂到了Nginx镜像的/etc/nginx/conf.d目录下。
Nginx的nginx.conf中定义了会加载conf.d下所有conf后缀的Nginx配置:

1
include /etc/nginx/conf.d/*.conf;

而Projected Volume的挂载是在容器启动步骤最开始就进行的。所以当容器启动之前,会从ConfigMap中获取default.conf配置文件,放到/etc/nginx/conf.d目录中。当Nginx进程启动的时候,就会读到该站点的配置。
既然Nginx的配置已经由ConfigMap提供,我们就可以不需要在源代码中包含。于是Dockerfile就被精简为:

1
2
FROM nginx:alpine
COPY ./dist /usr/share/nginx/html

不管Pod在哪台宿主机上,都可以访问到ConfigMap,所以我们很容易就能猜到ConfigMap的数据保存在etcd上。
除了以YAML方式定义,还可以通过–from-file参数将文件创建为ConfigMap。
在一般情况下ConfigMap会先覆盖掉挂载目录然后再将ConfigMap中的内容作为文件挂载进行。如果想要不覆盖原本文件夹下的文件可以使用subPath参数。

1.1 热更新

更新ConfigMap不会触发Pod的滚动更新,所以每次需要修改Pod Annotation的方式来强制触发滚动更新。具体命令如:

1
kubectl patch deployment <Deployment名> --patch '{"spec": {"template": {"metadata": {"annotations": {"version/config": "20180411" }}}}}'

更多可以参考:
ConfigMap的热更新

从这个角度来说,ConfigMap不太适合保存频繁更新的配置。

2. Secret

除了ConfigMap之外,还有一种Projected Volume:Secret。从名字就可以猜得到,保存的是敏感信息,包括密码,认证token,密钥key等。
像这些比较敏感的信息,直接写在Kubernetes的Deployment YAML定义里肯定不合适。放在Secret中会比较安全和灵活。除了保存信息是加密的之外,Secret和ConfigMap并没有太大差别。
Kubernetes官网有一个生成用户名和密码的简单范例:使用 Secret 安全地分发凭证 - Kubernetes,这里就不多复述了。
这里介绍Secret,主要是因为我们接下来要给网站添加强制HTTPS访问。
要开启HTTPS访问,就先需要一个SSL证书。如果我们没有SSL证书的话,可以自己签发一个:

1
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=traefik-ui.demosite.net"

然后我们就可以通过如下的命令,根据SSL证书生成Secret:

1
kubectl create secret tls traefik-cert --key=tls.key --cert=tls.crt -n kube-system

由于是供Traefik用的,所以创建在kube-system的namespace里。接下来我们修改Traefik配置。

3. 配置TLS

3.1 配置traefik.toml

我们接下来为Traefik Ingress Controller配置证书。
Traefik的配置文件是traefik.toml。我们按照第1节的方式,将其配置为ConfigMap:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
apiVersion: v1
kind: ConfigMap
metadata:
name: traefik-conf
namespace: kube-system
data:
traefik.toml: |
# 设置insecureSkipVerify = true,可以配置backend为443(比如dashboard)的ingress规则
insecureSkipVerify = true
defaultEntryPoints = ["http", "https"]
[entryPoints]
[entryPoints.http]
address = ":80"
### 配置http 强制跳转 https
[entryPoints.http.redirect]
entryPoint = "https"
### 配置只信任trustedIPs传递过来X-Forwarded-*,默认全部信任;为了防止客户端地址伪造,需开启这个
#[entryPoints.http.forwardedHeaders]
# trustedIPs = ["10.1.0.0/16", "172.20.0.0/16", "192.168.1.3"]
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
[[entryPoints.https.tls.certificates]]
CertFile = "/ssl/tls.crt"
KeyFile = "/ssl/tls.key"

其中的[entryPoints.http.redirect]就是强制重定向的配置。
更多可选配置可以参考官方文档

3.2 修改Traefik Ingress Controller配置

在Ingress Controller引入ConfigMap中的配置和Secret中的证书,增加443端口。先上个配置全文:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: traefik-ingress-controller
namespace: kube-system
---
kind: DaemonSet
apiVersion: extensions/v1beta1
metadata:
name: traefik-ingress-controller
namespace: kube-system
labels:
k8s-app: traefik-ingress-lb
spec:
template:
metadata:
labels:
k8s-app: traefik-ingress-lb
name: traefik-ingress-lb
spec:
serviceAccountName: traefik-ingress-controller
terminationGracePeriodSeconds: 60
volumes:
- name: ssl
secret:
secretName: traefik-cert
- name: config
configMap:
name: traefik-conf
containers:
- image: traefik
name: traefik-ingress-lb
volumeMounts:
- mountPath: "/ssl"
name: "ssl"
- mountPath: "/config"
name: "config"
ports:
- name: http
containerPort: 80
hostPort: 80
- name: admin
containerPort: 8080
hostPort: 8080
- name: https
containerPort: 443
hostPort: 443
securityContext:
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
args:
- --api
- --kubernetes
- --logLevel=INFO
- --configfile=/config/traefik.toml
nodeSelector:
node: edge
---
kind: Service
apiVersion: v1
metadata:
name: traefik-ingress-service
namespace: kube-system
spec:
selector:
k8s-app: traefik-ingress-lb
ports:
- protocol: TCP
port: 80
name: http
- protocol: TCP
port: 443
name: https
- protocol: TCP
port: 8080
name: admin

首先把ConfigMap和Secret以Projected Volume的形式挂上:

1
2
3
4
5
6
7
volumes:
- name: ssl
secret:
secretName: traefik-cert
- name: config
configMap:
name: traefik-conf

然后mount到container上:

1
2
3
4
5
volumeMounts:
- mountPath: "/ssl"
name: "ssl"
- mountPath: "/config"
name: "config"

在container和service里增加443端口:

1
2
3
4
5
volumeMounts:
- mountPath: "/ssl"
name: "ssl"
- mountPath: "/config"
name: "config"

启动参数里增加config文件:

1
2
3
4
5
args:
- --api
- --kubernetes
- --logLevel=INFO
- --configfile=/config/traefik.toml

因为不太确定怎么热加载Ingress Controller,所以我采用了先delete然后重新apply。然后我们就能以https形式访问了。
顺带着Traefik Admin UI也变成https了:
Traefik Admin UI(https)

4. 参考资料

其他加载ConfigMap的方式,以及加载为环境变量的Demo
K8S学习笔记之Kubernetes 配置管理 ConfigMap - 时光飞逝,逝者如斯 - 博客园

ConfigMap加载为命令行参数和非覆盖加载的Demo
Kubernetes对象之ConfigMap - 简书

官方的ConfigMap Demo
使用ConfigMap来配置Redis - Kubernetes

网上有些配置较为过时,配置后虽然没有报错,但访问https地址就是遇到Connection Refused。下面这篇比较新一些,是本篇的主要参考:
kubeasz/ingress-tls.md at master · easzlab/kubeasz

本文永久链接 [ https://galaxyyao.github.io/2019/07/03/容器-12-Kubernetes实战-静态网站部署优化1-ConfigMap-Secret与TLS/ ]