阅读本篇文章需要先完成上一篇:ACME 。
上一篇文章我们给Caddy和“其它“服务器配置了ACME协议,其实还有一种服务也具备ACME客户端功能,就是Kubernetes。这篇文章我们将使用cert-manager(下称cm)通过ACME协议从我们自己的CA获取证书。完成动手练习需要会简单使用kubectl
工具。
ℹ️ 本篇文章涉及一些镜像仓库,访问这些仓库可能需要不可描述的方法,请自行解决。
C Nginx Ingress
为了搭建实验环境,我们在Linux虚拟机上启动一个minikube,它将使用Docker作为容器(默认就这个)。使用云服务商提供的正经Kubernetes也行,不过那不是得花钱么。
输出应该是这样的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
😄 minikube v1.33.1 on Debian 12.6
✨ Automatically selected the docker driver
📌 Using Docker driver with root privileges
👍 Starting "minikube" primary control-plane node in "minikube" cluster
🚜 Pulling base image v0.0.44 ...
🔥 Creating docker container ( CPUs = 2, Memory = 2200MB) ...
🐳 Preparing Kubernetes v1.30.0 on Docker 26.1.1 ...
▪ Generating certificates and keys ...
▪ Booting up control plane ...
▪ Configuring RBAC rules ...
🔗 Configuring bridge CNI ( Container Networking Interface) ...
🔎 Verifying Kubernetes components...
▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5
🌟 Enabled addons: storage-provisioner, default-storageclass
🏄 Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default
ℹ️ macOS用户可以用OrbStack 来代替minikube,又好用又轻👍。
可以看看Docker容器的IP地址:
minikube天生就有nginx ingress:
1
minikube addons enable ingress
输出大概是这样的:
1
2
3
4
5
6
7
💡 ingress is an addon maintained by Kubernetes. For any concerns contact minikube on GitHub.
You can view the list of minikube maintainers at: https://github.com/kubernetes/minikube/blob/master/OWNERS
▪ Using image registry.k8s.io/ingress-nginx/controller:v1.10.1
▪ Using image registry.k8s.io/ingress-nginx/kube-webhook-certgen:v1.4.1
▪ Using image registry.k8s.io/ingress-nginx/kube-webhook-certgen:v1.4.1
🔎 Verifying ingress addon...
🌟 The 'ingress' addon is enabled
等几分钟可以看看状态:
1
kubectl get pods -n ingress-nginx
输出大概是这样的:
1
2
3
4
NAME READY STATUS RESTARTS AGE
ingress-nginx-admission-create-lh4n7 0/1 Completed 0 60s
ingress-nginx-admission-patch-lctx4 0/1 Completed 0 60s
ingress-nginx-controller-768f948f8f-zcb6j 1/1 Running 0 60s
ℹ️ 假如不是用minikube自带的nginx ingress,而是用其他方法安装的nginx ingress,默认可能是“LoadBalancer”,这样的话需要改一下配置:
1
2
kubectl get service --all-namespaces
kubectl patch svc <服务名字> -n <命名空间> -p '{"spec": {"type": "NodePort"}}'
D DNS:
这里我们需要“劫持”DNS解析,我们要让Vault机器以及cm能够将ingress的FQDN解析为Docker的IP地址。cm会在发起http-01挑战之前先自查,所以它自己也得能解析我们的ingress绑定的FQDN,上面示意图并没有包括这一步。
以下方法二选一:
改hosts文件
在minikube的主机 上给minikube的docker IP地址加一条:
1
sudo bash -c "echo \" $( minikube ip) hellonerd.miyunda.com\" >> /etc/hosts"
同样在Vault的机器 上也加一条同样的信息:
1
sudo bash -c "echo \"192.168.49.2 hellonerd.miyunda.com\" >> /etc/hosts"
以上域名是我们实验用的FQDN,证书也是给它申请的。
ℹ️ 使用云服务商提供的Kubernetes可以忽略这步——直接去公网DNS加一条解析即可。
在家里的DNS服务器上添加解析:
这个每个人家里都不见得一样,就自己研究吧,我的是OPNsense上运行的Dnsmasq:
⚠️ 大多数DNS服务器默认不许把“看起来像公网的FQDN”解析为私有IP地址。我们得关闭rebinding检查。关闭了就有安全隐患,不过反正我这DNS服务器也只是做实验用的,无所谓。家里的正经设备不要这么搞,实在不行直接用foobar.local
什么的算了。
E 部署一个app:
我选择使用Kuard 用作演示,一个轻巧的服务。
在minikube机器 上运行:
1
2
3
mkdir ~/hellonerd-certmanager-acme && cd ~/hellonerd-certmanager-acme
nano deploy.yaml
kubectl apply -f deploy.yaml
以上文件内容是:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
apiVersion : apps/v1
kind : Deployment
metadata :
name : kuard
spec :
selector :
matchLabels :
app : kuard
replicas : 1
template :
metadata :
labels :
app : kuard
spec :
containers :
- image : gcr.io/kuar-demo/kuard-amd64:blue
imagePullPolicy : Always
name : kuard
ports :
- containerPort : 8080
还得给Kuard准备一个服务:
1
2
nano service.yaml
kubectl apply -f service.yaml
文件内容如下:
1
2
3
4
5
6
7
8
9
10
11
apiVersion : v1
kind : Service
metadata :
name : kuard
spec :
ports :
- port : 80
targetPort : 8080
protocol : TCP
selector :
app : kuard
还有ingress,得让控制器知道如何绑定域名并向后端(Kuard)转发流量:
1
2
nano ingress.yaml
kubectl apply -f ingress.yaml
第12行我们约定了敏感信息将存在hellonerd-example-tls
,不过这不会生效,因为minikube不知道哪里有证书签发者(issuer),见第5行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
apiVersion : networking.k8s.io/v1
kind : Ingress
metadata :
name : kuard
annotations : {}
# cert-manager.io/issuer: "vault-dev"
spec :
ingressClassName : nginx
tls :
- hosts :
- hellonerd.miyunda.com
secretName : hellonerd-example-tls
rules :
- host : hellonerd.miyunda.com
http :
paths :
- path : /
pathType : Prefix
backend :
service :
name : kuard
port :
number : 80
等亿分钟之后查看状态:
状态应该是这样的:
1
2
NAME CLASS HOSTS ADDRESS PORTS AGE
kuard nginx hellonerd.miyunda.com 192.168.49.2 80, 443 2m4s
ℹ️ 使用云服务商的同学会看到公网地址。
接下来在minikube主机 上开启IPv4转发:
1
2
sudo sysctl net.ipv4.ip_forward # sudo sysctl -w net.ipv4.ip_forward=1
sudo iptables -P FORWARD ACCEPT
在Vault主机 添加静态路由:
1
sudo route add -host 192.168.49.2 gw 192.168.3.7 # minikube机器接入家庭网络的网卡的IP地址
ℹ️ 以上一条命令是为了Vault主机能访问minikube里面的Docker容器地址,要是它们两个装在同一个机器可以忽略。使用云服务商提供的Kubernets当然也可以忽略。
在minikube主机和Vault主机都试试访问:
1
curl -kivL hellonerd.miyunda.com
可以看到此时ingress的控制器不知道去哪能搞一个证书,于是就吃铁丝拉笊篱——现编了一个:
1
2
3
4
5
6
7
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN: server accepted h2
* Server certificate:
* subject: O = Acme Co; CN = Kubernetes Ingress Controller Fake Certificate
* start date: Aug 27 11:29:07 2024 GMT
* expire date: Aug 27 11:29:07 2025 GMT
* issuer: O = Acme Co; CN = Kubernetes Ingress Controller Fake Certificate
而说好的敏感信息也无处寻觅:
1
2
kubectl describe secret hellonerd-example-tls
Error from server ( NotFound) : secrets "hellonerd-example-tls" not found
F cert-manager:
cert-manager是Kubernetes的数字证书集散中心,它从CA(或者Venafi什么的)提供的API获取数字证书,并按我们的指示安全地存好,ingress可以从存储中获取证书。
安装
安装方法有几种,我用helm
完成:
在Debian上安装helm:
1
2
3
4
5
6
curl https://baltocdn.com/helm/signing.asc | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null
sudo apt install apt-transport-https --yes
echo "deb [arch= $( dpkg --print-architecture) signed-by=/usr/share/keyrings/helm.gpg] https://baltocdn.com/helm/stable/debian/ all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list
sudo apt update
sudo apt install helm
helm version
安装cm:
1
helm repo add jetstack https://charts.jetstack.io --force-update
1
2
3
4
5
6
7
helm install \
cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--version v1.15.3 \
--set crds.enabled= true \
--set prometheus.enabled= false
注册ACME账号:
准备一个文件:
1
2
nano issuer.yaml
kubectl create -f issuer.yaml
内容是:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion : cert-manager.io/v1
kind : Issuer
metadata :
name : vault-dev # 随意命名
spec :
acme :
# Vault的ACME服务地址
server : https://vault-dev.miyunda.com/v1/ca-int-x1/acme/directory
# 邮件地址,因为我们没有发邮件的服务,所以无所谓
email : 404 @miyunda.com
# 在ACME服务端注册后会有账号,账号的密钥存这里
privateKeySecretRef :
name : vault-dev
# 使用http-01作为挑战方式
solvers :
- selector : {}
http01 :
ingress :
ingressClassName : nginx
看看它的状态:
输出必须如下:
1
2
NAME READY AGE
vault-dev True 2s
不行的话就要看看详细信息:
1
kubectl describe issuer vault-dev
这样的:
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
Name: vault-dev
Namespace: default
Labels: <none>
Annotations: <none>
API Version: cert-manager.io/v1
Kind: Issuer
Metadata:
Creation Timestamp: 2024-08-22T08:05:47Z
Generation: 1
Resource Version: 5097
UID: 2075851d-ee06-4591-8562-dde715749a85
Spec:
Acme:
Email: [email protected]
Private Key Secret Ref:
Name: vault-dev
Server: https://vault-dev.miyunda.com/v1/ca-int-x1/acme/directory
Solvers:
http01:
Ingress:
Ingress Class Name: nginx
Selector:
Status:
Acme:
Last Private Key Hash: Vo4NFyJrVngYt+Is02yZ3qi3H4kW8rbjEh9OWnRl0T0=
Last Registered Email: [email protected]
Uri: https://vault-dev.miyunda.com/v1/ca-int-x1/acme/account/0a4a3c8a-fc9a-53ba-738f-74b4e1432820
Conditions:
Last Transition Time: 2024-08-22T08:05:48Z
Message: The ACME account was registered with the ACME server
Observed Generation: 1
Reason: ACMEAccountRegistered
Status: True
Type: Ready
Events: <none>
给ingress申请及安装数字证书
先删了刚才创建的ingress:
1
kubectl delete ingress kuard
然后编辑下那个文件ingress.yaml
,去掉第6行的注释:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
apiVersion : networking.k8s.io/v1
kind : Ingress
metadata :
name : kuard
annotations :
cert-manager.io/issuer : "vault-dev"
spec :
ingressClassName : nginx
tls :
- hosts :
- hellonerd.miyunda.com
secretName : hellonerd-example-tls
rules :
- host : hellonerd.miyunda.com
http :
paths :
- path : /
pathType : Prefix
backend :
service :
name : kuard
port :
number : 80
再次创建:
1
kubectl apply -f ingress.yaml
第12行我们指定了敏感信息保存位置,这时它还只有证书hellonerd.miyunda.com
的私钥(cm发CSR到Vault,Vault不存这个证书的私钥):
1
2
3
4
5
6
7
8
9
10
11
12
kubectl describe secret hellonerd-example-tls-vvnk8
Name: hellonerd-example-tls-vvnk8
Namespace: default
Labels: cert-manager.io/next-private-key= true
controller.cert-manager.io/fao= true
Annotations: <none>
Type: Opaque
Data
====
tls.key: 1704 bytes
等几分钟再去看就看到它多了个证书本书:
1
2
3
4
5
6
7
8
9
10
kubectl describe secret hellonerd-example-tls
Name: hellonerd-example-tls
Namespace: default
...
Data
====
tls.crt: 4709 bytes
tls.key: 1675 bytes
再次去访问网站:
1
curl -kivL hellonerd.miyunda.com
我们可以看到,网站已经绑定了我们CA签发的数字证书:
1
2
3
4
5
6
7
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN: server accepted h2
* Server certificate:
* subject: [ NONE]
* start date: Aug 27 12:25:26 2024 GMT
* expire date: Sep 28 12:25:56 2024 GMT
* issuer: OU = Marketing; CN = vault-dev.miyunda.com Intermediate Authority
这也符合下面第9、10行这里的描述:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
kubectl describe ingress kuard
Name: kuard
Labels: <none>
Namespace: default
Address: 192.168.49.2
Ingress Class: nginx
Default backend: <default>
TLS:
hellonerd-example-tls terminates hellonerd.miyunda.com
Rules:
Host Path Backends
---- ---- --------
hellonerd.miyunda.com
/ kuard:80 ( 10.244.0.6:8080)
Annotations: cert-manager.io/issuer: vault-dev
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal CreateCertificate 50m cert-manager-ingress-shim Successfully created Certificate "hellonerd-example-tls"
Normal Sync 49m ( x2 over 50m) nginx-ingress-controller Scheduled for sync
排错:
列出数字证书:
1
kubectl get certificate
输出如下:
1
2
NAME READY SECRET AGE
hellonerd-example-tls True hellonerd-example-tls 9m55s
看看它的信息:
1
kubectl describe certificate hellonerd-example-tls
输出如下:
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
kubectl describe certificate hellonerd-example-tls
Name: hellonerd-example-tls
Namespace: default
Labels: <none>
Annotations: <none>
API Version: cert-manager.io/v1
Kind: Certificate
Metadata:
Creation Timestamp: 2024-08-22T08:05:23Z
Generation: 1
Owner References:
API Version: networking.k8s.io/v1
Block Owner Deletion: true
Controller: true
Kind: Ingress
Name: kuard
UID: d2f61502-cbfc-430f-9694-b63c9ee9d3c5
Resource Version: 5179
UID: d3284be3-b26e-429a-8933-d83d5cff5365
Spec:
Dns Names:
hellonerd.miyunda.com
Issuer Ref:
Group: cert-manager.io
Kind: Issuer
Name: vault-dev
Secret Name: hellonerd-example-tls
Usages:
digital signature
key encipherment
Status:
Conditions:
Last Transition Time: 2024-08-22T08:06:06Z
Message: Certificate is up to date and has not expired
Observed Generation: 1
Reason: Ready
Status: True
Type: Ready
Not After: 2024-09-23T08:06:06Z
Not Before: 2024-08-22T08:05:36Z
Renewal Time: 2024-09-12T16:05:56Z
Revision: 1
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Issuing 13m cert-manager-certificates-trigger Issuing certificate as Secret does not exist
Normal Generated 13m cert-manager-certificates-key-manager Stored new private key in temporary Secret resource "hellonerd-example-tls-bh5gv"
Normal Requested 13m cert-manager-certificates-request-manager Created new CertificateRequest resource "hellonerd-example-tls-1"
Normal Issuing 12m cert-manager-certificates-issuing The certificate has been successfully issued
可以看看请求证书的状态:
1
kubectl get certificaterequests # --all-namespaces
1
2
NAME APPROVED DENIED READY ISSUER REQUESTOR AGE
hellonerd-example-tls-1 True True vault-dev system:serviceaccount:cert-manager:cert-manager 11m
1
kubectl describe certificaterequest hellonerd-example-tls-1
也可以看看Order:
1
kubectl get orders.acme.cert-manager.io
1
2
NAME STATE AGE
hellonerd-example-tls-1-3907671000 valid 11m
1
kubectl describe order hellonerd-example-tls-1-3907671000
好了,看过了等于会了。感谢观看。❤️
这篇文章我们演示了如何使用cm的ACME能力获取我们自己CA的数字证书。下一篇我不用ACME了,因为cm也能直接与Vault交互,而不是使用ACME协议。