Kubernetes 高级运维
# Kubernetes高级运维
# 使用kubeadm进行证书管理
由 kubeadm (opens new window) 生成的客户端证书在 1 年后到期。 本页说明如何使用 kubeadm 管理证书续订。
# 使用自定义的证书
- 默认情况下, kubeadm 会生成运行一个集群所需的全部证书。 你可以通过提供你自己的证书来改变这个行为策略。
- 如果要这样做, 你必须将证书文件放置在通过
--cert-dir
命令行参数或者 kubeadm 配置中的CertificatesDir
配置项指明的目录中。默认的值是/etc/kubernetes/pki
。 - 如果在运行
kubeadm init
之前存在给定的证书和私钥对,kubeadm 将不会重写它们。 例如,这意味着您可以将现有的 CA 复制到/etc/kubernetes/pki/ca.crt
和/etc/kubernetes/pki/ca.key
中,而 kubeadm 将使用此 CA 对其余证书进行签名。
# 外部 CA 模式
- 只提供了
ca.crt
文件但是不提供ca.key
文件也是可以的 (这只对 CA 根证书可用,其它证书不可用)。 如果所有的其它证书和 kubeconfig 文件已就绪,kubeadm 检测到满足以上条件就会激活 "外部 CA" 模式。kubeadm 将会在没有 CA 密钥文件的情况下继续执行。 - 否则, kubeadm 将独立运行 controller-manager,附加一个
--controllers=csrsigner
的参数,并且指明 CA 证书和密钥。 - PKI 证书和要求 (opens new window)包括集群使用外部 CA 的设置指南。
# 检查证书是否过期
- 你可以使用
check-expiration
子命令来检查证书何时过期 - 该命令显示
/etc/kubernetes/pki
文件夹中的客户端证书以及 kubeadm(admin.conf
,controller-manager.conf
和scheduler.conf
) 使用的 KUBECONFIG 文件中嵌入的客户端证书的到期时间/剩余时间。
[root@master ~]# kubeadm certs check-expiration
[check-expiration] Reading configuration from the cluster...
[check-expiration] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
CERTIFICATE EXPIRES RESIDUAL TIME CERTIFICATE AUTHORITY EXTERNALLY MANAGED
admin.conf Nov 23, 2022 18:55 UTC 363d no
apiserver Nov 23, 2022 18:55 UTC 363d ca no
apiserver-etcd-client Nov 23, 2022 18:55 UTC 363d etcd-ca no
apiserver-kubelet-client Nov 23, 2022 18:55 UTC 363d ca no
controller-manager.conf Nov 23, 2022 18:55 UTC 363d no
etcd-healthcheck-client Nov 23, 2022 18:55 UTC 363d etcd-ca no
etcd-peer Nov 23, 2022 18:55 UTC 363d etcd-ca no
etcd-server Nov 23, 2022 18:55 UTC 363d etcd-ca no
front-proxy-client Nov 23, 2022 18:55 UTC 363d front-proxy-ca no
scheduler.conf Nov 23, 2022 18:55 UTC 363d no
CERTIFICATE AUTHORITY EXPIRES RESIDUAL TIME EXTERNALLY MANAGED
ca Nov 21, 2031 18:55 UTC 9y no
etcd-ca Nov 21, 2031 18:55 UTC 9y no
front-proxy-ca Nov 21, 2031 18:55 UTC 9y no
# 自动更新证书
kubeadm
会在控制面 升级 (opens new window) 的时候更新所有证书。- 这个功能旨在解决最简单的用例;如果你对此类证书的更新没有特殊要求,并且定期执行Kubernetes版本升级(每次升级之间的间隔时间少于1年),kubeadm将确保你的集群保持最新状态并保持合理的安全性。
- 说明: 最佳的做法是经常升级集群以确保安全。
- 如果你对证书更新有更复杂的需求,则可通过将
--certificate-renewal=false
传递给kubeadm upgrade apply
或者kubeadm upgrade node
,从而选择不采用默认行为。
# 手动更新证书
你能随时通过
kubeadm certs renew
命令手动更新你的证书。此命令用 CA (或者 front-proxy-CA )证书和存储在
/etc/kubernetes/pki
中的密钥执行更新。执行完此命令之后你需要重启控制面 Pods。因为动态证书重载目前还不被所有组件和证书支持,所有这项操作是必须的。 静态 Pods (opens new window) 是被本地 kubelet 而不是 API Server 管理, 所以 kubectl 不能用来删除或重启他们。 要重启静态 Pod 你可以临时将清单文件从
/etc/kubernetes/manifests/
移除并等待 20 秒 (参考 KubeletConfiguration 结构 (opens new window) 中的fileCheckFrequency
值)。 如果 Pod 不在清单目录里,kubelet将会终止它。 在另一个fileCheckFrequency
周期之后你可以将文件移回去,为了组件可以完成 kubelet 将重新创建 Pod 和证书更新。警告: 如果你运行了一个 HA 集群,这个命令需要在所有控制面板节点上执行。
说明:
certs renew
使用现有的证书作为属性 (Common Name、Organization、SAN 等) 的权威来源, 而不是 kubeadm-config ConfigMap 。强烈建议使它们保持同步。kubeadm certs renew
提供以下选项:Kubernetes 证书通常在一年后到期。
--csr-only
可用于经过一个外部 CA 生成的证书签名请求来更新证书(无需实际替换更新证书)。可以更新单个证书而不是全部证书。
# 证书
在使用客户端证书认证的场景下,你可以通过 easyrsa
、openssl
或 cfssl
等工具以手工方式生成证书。
# cfssl方式
- cfssl下载地址
VERSION=R1.2
for i in {cfssl,cfssljson,cfssl-certinfo}
do
wget https://pkg.cfssl.org/${VERSION}/${i}_linux-amd64 -O /usr/local/bin/${i}
done
- 生成CA配置文件
mkdir ssl && cd ssl
cfssl print-defaults config > config.json
cfssl print-defaults csr > csr.json
cat > ca-config.json <<EOF
{
"signing": {
"default": {
"expiry": "87600h"
},
"profiles": {
"kubernetes": {
"usages": [
"signing",
"key encipherment",
"server auth",
"client auth"
],
"expiry": "87600h"
}
}
}
}
EOF
- 生成CA签名配置文件
cat > ca-csr.json << EOF
{
"CN": "kubernetes",
"key": {
"algo": "rsa",
"size": 2048
},
"names":[{
"C": "CN",
"ST": "Beijing",
"L": "BeiJing",
"O": "k8s",
"OU": "System"
}]
}
EOF
- 生成私钥和证书
cfssl gencert -initca ca-csr.json | cfssljson -bare ca
- 创建一个用于生成API Server的密钥和证书的JSON配置文件
cat > kubernetes-csr.json <<EOF
{
"CN": "kubernetes",
"hosts": [
"127.0.0.1",
"<MASTER_IP>",
"<MASTER_CLUSTER_IP>",
"kubernetes",
"kubernetes.default",
"kubernetes.default.svc",
"kubernetes.default.svc.cluster",
"kubernetes.default.svc.cluster.local"
],
"key": {
"algo": "rsa",
"size": 2048
},
"names": [{
"C": "CN",
"ST": "Beijing",
"L": "BeiJing",
"O": "k8s",
"OU": "System"
}]
}
EOF
#该文件需要包含所有使用该证书的ip和域名列表,包括etcd集群、kubernetes master集群、以及apiserver 集群内部cluster ip。
- 生成 kubernetes 证书和私钥
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=kubernetes kubernetes-csr.json | cfssljson -bare kubernetes
# 创建admin证书
cat > admin-csr.json <<EOF
{
"CN": "admin",
"hosts": [],
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "CN",
"ST": "BeiJing",
"L": "BeiJing",
"O": "system:masters",
"OU": "System"
}
]
}
EOF
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=kubernetes admin-csr.json | cfssljson -bare admin
# 证书O配置为system:masters 在集群内部cluster-admin的clusterrolebinding将system:masters组和cluster-admin clusterrole绑定在一起
Copy
# 创建kube-proxy证书
cat > kube-proxy-csr.json << EOF
{
"CN": "system:kube-proxy",
"hosts": [],
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "CN",
"ST": "BeiJing",
"L": "BeiJing",
"O": "k8s",
"OU": "System"
}
]
}
EOF
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=kubernetes kube-proxy-csr.json | cfssljson -bare kube-proxy
# 该证书用户名为system:kube-proxy,预定义的system:node-proxier clusterrolebindings将 system:kube-proxy用户和system:node-proxier角色绑定在一起
Copy
# 校验证书信息
cfssl-certinfo -cert kubernetes.pem
openssl x509 -noout -text -in kubernetes.pem
Copy
# 拷贝证书
mkdir -p /etc/kubernetes/ssl/ && cp *.pem /etc/kubernetes/ssl/
# easyrsa方式
easyrsa 支持以手工方式为你的集群生成证书。
下载、解压、初始化打过补丁的 easyrsa3。
[root@master pod]# curl -O http://172.25.253.144/file/tranning/easy-rsa.tar.gz
[root@master pod]# tar xzf easy-rsa.tar.gz
[root@master pod]# cd easy-rsa-master/easyrsa3
[root@master easyrsa3]# ./easyrsa init-pki
- 生成新的证书颁发机构(CA)。参数
--batch
用于设置自动模式; 参数--req-cn
用于设置新的根证书的通用名称(CN)。
[root@master easyrsa3]# ./easyrsa --batch "--req-cn=172.25.253.3@`date +%s`" build-ca nopass
Generating a 2048 bit RSA private key
...+++
..........+++
writing new private key to '/root/pod/easy-rsa-master/easyrsa3/pki/private/ca.key'
-----
- 生成服务器证书和秘钥。 参数
--subject-alt-name
设置API服务器的IP和 DNS 名称。MASTER_CLUSTER_IP
用于 API 服务器和控制管理器,通常取 CIDR 的第一个 IP,由--service-cluster-ip-range
的参数提供。 参数--days
用于设置证书的过期时间。 下面的示例假定你的默认 DNS 域名为cluster.local
。
[root@master easyrsa3]# ./easyrsa --subject-alt-name="IP:172.25.253.3,"\
> "IP:10.96.0.1,"\
> "DNS:kubernetes,"\
> "DNS:kubernetes.default,"\
> "DNS:kubernetes.default.svc,"\
> "DNS:kubernetes.default.svc.cluster,"\
> "DNS:kubernetes.default.svc.cluster.local" \
> --days=10000 \
> build-server-full server nopass
Generating a 2048 bit RSA private key
.......................+++
.................+++
writing new private key to '/root/pod/easy-rsa-master/easyrsa3/pki/private/server.key'
-----
Using configuration from /root/pod/easy-rsa-master/easyrsa3/openssl-1.0.cnf #这里有告诉你的证书路径
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
commonName :ASN.1 12:'server'
Certificate is to be certified until Apr 11 20:46:17 2049 GMT (10000 days)
Write out database with 1 new entries
Data Base Updated
- 拷贝文件
pki/ca.crt
、pki/issued/server.crt
和pki/private/server.key
到你的目录中。 - 在 API 服务器的启动参数中添加以下参数:
--client-ca-file=/yourdirectory/ca.crt
--tls-cert-file=/yourdirectory/server.crt
--tls-private-key-file=/yourdirectory/server.key
- admin证书
[root@master ~]# openssl genrsa -out admin.key 2048
[root@master ~]# openssl req -new -key admin.key -out admin.csr -subj "/O=system:masters/CN=dmin"
[root@master ~]# openssl x509 -req -set_serial $(date +%s%N) -in admin.csr -CA ca.crt -CAkey ca.key -out admin.crt -days 365 -extensions v3_req -extfile req.conf
# openssl方式
openssl 支持以手工方式为你的集群生成证书。
- 生成一个 2048 位的 ca.key 文件
[root@master ~]# openssl genrsa -out ca.key 2048
- 在ca.key文件的基础上,生成ca.crt文件(用参数 -days 设置证书有效期)
[root@master ~]# openssl req -x509 -new -nodes -key ca.key -subj "/CN=${MASTER_IP}" -days 10000 -out ca.crt
- 生成一个2048位的server.key文件:
[root@master ~]# openssl genrsa -out server.key 2048
- 创建一个用于生成证书签名请求(CSR)的配置文件。 保存文件(例如:
csr.conf
)前,记得用真实值替换掉尖括号中的值(例如:<MASTER_IP>
)。 注意:MASTER_CLUSTER_IP
就像前一小节所述,它的值是 API 服务器的服务集群 IP。 下面的例子假定你的默认 DNS 域名为cluster.local
。
[root@master ~]# cat csr.conf
[ req ]
default_bits = 2048
prompt = no
default_md = sha256
req_extensions = req_ext
distinguished_name = dn
[ dn ]
C = <country>
ST = <state>
L = <city>
O = <organization>
OU = <organization unit>
CN = 172.25.253.3
[ req_ext ]
subjectAltName = @alt_names
[ alt_names ]
DNS.1 = kubernetes
DNS.2 = kubernetes.default
DNS.3 = kubernetes.default.svc
DNS.4 = kubernetes.default.svc.cluster
DNS.5 = kubernetes.default.svc.cluster.local
IP.1 = 172.25.253.3
IP.2 = 10.96.0.1
[ v3_ext ]
authorityKeyIdentifier=keyid,issuer:always
basicConstraints=CA:FALSE
keyUsage=keyEncipherment,dataEncipherment
extendedKeyUsage=serverAuth,clientAuth
subjectAltName=@alt_names
- 基于上面的配置文件生成证书签名请求:
[root@master ~]# openssl req -new -key server.key -out server.csr -config csr.conf
- 基于 ca.key、ca.key 和 server.csr 等三个文件生成服务端证书:
[root@master ~]# openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key \
-CAcreateserial -out server.crt -days 10000 \
-extensions v3_ext -extfile csr.conf
- 查看证书签名请求:
[root@master ~]# openssl req -noout -text -in ./server.csr
- 查看证书:
[root@master ~]# openssl x509 -noout -text -in ./server.crt
admin证书
[root@master ~]# openssl genrsa -out admin.key 2048
[root@master ~]# openssl req -new -key admin.key -out admin.csr -subj "/O=system:masters/CN=dmin"
[root@master ~]# openssl x509 -req -set_serial $(date +%s%N) -in admin.csr -CA ca.crt -CAkey ca.key -out admin.crt -days 365 -extensions v3_req -extfile req.conf
# 健康检查
# 配置存活、就绪和启动探测器
kubelet (opens new window) 使用存活探测器来知道什么时候要重启容器,存活探测器可以捕捉到死锁(应用程序在运行,但是无法继续执行后面的步骤)。 这样的情况下重启容器有助于让应用程序在有问题的情况下更可用。
kubelet 使用就绪探测器可以知道容器什么时候准备好了并可以开始接受请求流量, 当一个 Pod 内的所有容器都准备好了,才能把这个 Pod 看作就绪了。 这种信号的一个用途就是控制哪个 Pod 作为 Service 的后端。 在 Pod 还没有准备好的时候,会从 Service 的负载均衡器中被剔除的。
kubelet 使用启动探测器可以知道应用程序容器什么时候启动了。 如果配置了这类探测器,就可以控制容器在启动成功后再进行存活性和就绪检查, 确保这些存活、就绪探测器不会影响应用程序的启动。 这可以用于对慢启动容器进行存活性检测,避免它们在启动运行之前就被杀掉。
# 定义存活命令
许多长时间运行的应用程序最终会过渡到断开的状态,除非重新启动,否则无法恢复。 Kubernetes 提供了存活探测器来发现并补救这种情况。
在这个配置文件中,可以看到 Pod 中只有一个容器。
periodSeconds
字段指定了 kubelet 应该每 5 秒执行一次存活探测。initialDelaySeconds
字段告诉 kubelet 在执行第一次探测前应该等待 5 秒。kubelet 在容器内执行命令
cat /tmp/healthy
来进行探测。如果命令执行成功并且返回值为 0,kubelet 就会认为这个容器是健康存活的。 如果这个命令返回非 0 值,kubelet 会杀死这个容器并重新启动它。
当容器启动时,执行如下的命令:
/bin/sh -c "touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600"
这个容器生命的前 30 秒,
/tmp/healthy
文件是存在的。 所以在这最开始的 30 秒内,执行命令cat /tmp/healthy
会返回成功代码。 30 秒之后,执行命令cat /tmp/healthy
就会返回失败代码。apiVersion: v1 kind: Pod metadata: labels: test: liveness name: liveness-exec spec: containers: - name: liveness image: k8s.gcr.io/busybox args: - /bin/sh - -c - touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600 livenessProbe: exec: command: - cat - /tmp/healthy initialDelaySeconds: 5 periodSeconds: 5
# 定义一个存活态HTTP请求接口
另外一种类型的存活探测方式是使用 HTTP GET 请求。 下面是一个 Pod 的配置文件,其中运行一个基于 k8s.gcr.io/liveness
镜像的容器。
# 定义HTTP请求接口
- 在这个配置文件中,可以看到 Pod 也只有一个容器。
periodSeconds
字段指定了 kubelet 每隔 3 秒执行一次存活探测。initialDelaySeconds
字段告诉 kubelet 在执行第一次探测前应该等待 3 秒。- kubelet 会向容器内运行的服务(服务会监听 8080 端口)发送一个 HTTP GET 请求来执行探测。 如果服务器上
/healthz
路径下的处理程序返回成功代码,则 kubelet 认为容器是健康存活的。 如果处理程序返回失败代码,则 kubelet 会杀死这个容器并且重新启动它。
apiVersion: v1
kind: Pod
metadata:
labels:
test: liveness
name: liveness-http
spec:
containers:
- name: liveness
image: k8s.gcr.io/liveness
args:
- /server
livenessProbe: #周期性的
httpGet:
path: /healthz
port: 8080
httpHeaders:
- name: Custom-Header
value: Awesome
initialDelaySeconds: 3
periodSeconds: 3
kubelet在容器启动之后3秒开始执行健康检测。所以前几次健康检查都是成功的。但是10秒之后,健康检查会失败,并且kubelet会杀死容器再重新启动容器。
# 定义TCP的存活探测
第三种类型的存活探测是使用 TCP 套接字。 通过配置,kubelet 会尝试在指定端口和容器建立套接字链接。 如果能建立连接,这个容器就被看作是健康的,如果不能则这个容器就被看作是有问题的。
# 定义TCP探针
- 如下实例,kubelet 会在**
容器启动 5 秒后发送第一个就绪探测
。** - 这会尝试连接
nginx
容器的80端口。 如果探测成功,这个 Pod 会被标记为就绪状态,kubelet 将继续每隔 10 秒运行一次检测。 - 除了就绪探测,
这个配置包括了一个存活探测。
- kubelet 会在**
容器启动 15 秒后进行第一次存活探测。
** 与就绪探测类似,会尝试连接**nginx
**容器的80端口。 如果存活探测失败,这个容器会被重新启动。
[root@master ~]# cat tcp.yaml
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- image: nginx
name: nginx
ports:
- containerPort: 80
readinessProbe: #定期性的
tcpSocket:
port: 80
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
tcpSocket:
port: 80
initialDelaySeconds: 15
periodSeconds: 20
- 查看状态的转变
[root@master ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx 0/1 Running 0 27s
[root@master ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx 1/1 Running 0 65s
# 定义就绪探测器
有时候,应用程序会暂时性的不能提供通信服务。 例如,应用程序在启动时可能需要加载很大的数据或配置文件,或是启动后要依赖等待外部服务。 在这种情况下,既不想杀死应用程序,也不想给它发送请求。 Kubernetes 提供了就绪探测器来发现并缓解这些情况。 容器所在 Pod 上报还未就绪的信息,并且不接受通过 Kubernetes Service 的流量。就绪探测器在容器的整个生命周期中保持运行状态。
注意: 活跃性探测器 不等待 就绪性探测器成功。 如果要在执行活跃性探测器之前等待,应该使用 initialDelaySeconds 或 startupProbe。
就绪探测器的配置和存活探测器的配置相似。 唯一区别就是要使用 readinessProbe
字段,而不是 livenessProbe
字段。
readinessProbe:
exec:
command:
- cat
- /tmp/healthy
initialDelaySeconds: 5
periodSeconds: 5
# 配置探测器
# Probe (opens new window) 有很多配置字段,可以使用这些字段精确的控制存活和就绪检测的行为:
initialDelaySeconds
:容器启动后要等待多少秒后存活和就绪探测器才被初始化,默认是 0 秒,最小值是 0。periodSeconds
:执行探测的时间间隔(单位是秒)。默认是 10 秒。最小值是 1。timeoutSeconds
:探测的超时后等待多少秒。默认值是 1 秒。最小值是 1。successThreshold
:探测器在失败后,被视为成功的最小连续成功数。默认值是 1。 存活和启动探测的这个值必须是 1。最小值是 1。failureThreshold
:当探测失败时,Kubernetes 的重试次数。 存活探测情况下的放弃就意味着重新启动容器。 就绪探测情况下的放弃 Pod 会被打上未就绪的标签。默认值是 3。最小值是 1。
# HTTP 探测
# HTTP Probes (opens new window) 可以在
httpGet
上配置额外的字段:host
:连接使用的主机名,默认是 Pod 的 IP。也可以在 HTTP 头中设置 “Host” 来代替。scheme
:用于设置连接主机的方式(HTTP 还是 HTTPS)。默认是 HTTP。path
:访问 HTTP 服务的路径。默认值为 "/"。httpHeaders
:请求中自定义的 HTTP 头。HTTP 头字段允许重复。port
:访问容器的端口号或者端口名。如果数字必须在 1 ~ 65535 之间。
你可以通过为探测设置 .httpHeaders
来重载默认的头部字段值;例如:
livenessProbe:
httpGet:
httpHeaders:
- name: Accept
value: application/json
startupProbe:
httpGet:
httpHeaders:
- name: User-Agent
value: MyUserAgent
你也可以通过将这些头部字段定义为空值,从请求中去掉这些头部字段。
livenessProbe:
httpGet:
httpHeaders:
- name: Accept
value: ""
startupProbe:
httpGet:
httpHeaders:
- name: User-Agent
value: ""
# TCP 探测
对于一次 TCP 探测,kubelet 在节点上(不是在 Pod 里面)建立探测连接, 这意味着你不能在 host
参数上配置服务名称,因为 kubelet 不能解析服务名称。
# 探测器级别
terminationGracePeriodSeconds
- FEATURE STATE:
Kubernetes v1.22 [beta]
- 在 1.21 及更高版本中,当特性门控
ProbeTerminationGracePeriod
为 启用状态时,用户可以指定一个探测级别的terminationGracePeriodSeconds
作为 探针规格的一部分。 - 当特性门控被启用时,并且 Pod 级和探针级的
terminationGracePeriodSeconds
都已设置,kubelet 将 使用探针级设置的值。
- FEATURE STATE:
[root@master ~]# cat http_probe.yaml
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- image: 172.25.253.3/library/nginx
name: nginx
ports:
- containerPort: 80
name: liveness-port
hostPort: 80
livenessProbe:
httpGet:
path: /healthz
port: liveness-port
failureThreshold: 1 #在测试后被视为失败的探针的最小连续故障数成功的
periodSeconds: 60
terminationGracePeriodSeconds: 60 #可选的持续时间(秒),pod需要在探头故障宽限期是在秒之后的持续时间。
[root@master ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-654754dd8d-hnm2g 1/1 Running 0 66m
nginx 1/1 Running 0 3s
- 如果你已经为现有 Pod 设置了 “terminationGracePeriodSeconds” 字段并且 不再希望使用针对每个探针的终止宽限期,则必须删除那些现有的 Pod。
- (控制平面或某些其他组件)创建替换Pods,并且特性门控“ProbeTerminationGracePeriod”被禁用,那么API服务器会忽略Pod级别的
terminationGracePeriodSeconds
字段,即使Pod模板指定了它。
探测器级别的 terminationGracePeriodSeconds
不能用于设置就绪态探针。 它将被 API 服务器拒绝。
[root@master ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-654754dd8d-hnm2g 1/1 Running 0 72m
nginx 0/1 CrashLoopBackOff 5 (0s ago) 6m1s
# 使用RBAC鉴权
基于角色(Role)的访问控制(RBAC)是一种基于组织中用户的角色来调节控制对 计算机或网络资源的访问的方法。
RBAC 鉴权机制使用 rbac.authorization.k8s.io
API 组 (opens new window) 来驱动鉴权决定,允许你通过 Kubernetes API 动态配置策略。
要启用 RBAC,在启动 API 服务器 (opens new window) 时将 --authorization-mode
参数设置为一个逗号分隔的列表并确保其中包含 RBAC
。
# API 对象
RBAC API 声明了四种 Kubernetes 对象:Role、ClusterRole、RoleBinding 和 ClusterRoleBinding。你可以像使用其他 Kubernetes 对象一样, 通过类似 kubectl
这类工具 描述对象 (opens new window), 或修补对象。
# Role和ClusterRole
RBAC 的 Role 或 ClusterRole 中包含一组代表相关权限的规则
。 这些权限是纯粹累加的(不存在拒绝某操作的规则)
。
Role 总是用来在某个名字空间 (opens new window) 内设置访问权限;在你创建 Role 时,你必须指定该 Role 所属的名字空间。
与之相对,ClusterRole 则是一个集群作用域的资源。这两种资源的名字不同(Role 和 ClusterRole)是因为 Kubernetes 对象要么是名字空间作用域的,要么是集群作用域的, 不可两者兼具。
ClusterRole 有若干用法。你可以用它来:
- 定义对某名字空间域对象的访问权限,并将在各个名字空间内完成授权;
- 为名字空间作用域的对象设置访问权限,并跨所有名字空间执行授权;
- 为集群作用域的资源定义访问权限。
# Role的示例
下面是一个位于 "default" 名字空间的 Role 的示例,可用来授予对 pods (opens new window) 的读访问权限:
[root@master role]# kubectl create role pod-reader --namespace=default --resource=pods --verb=get,watch,list --dry-run -o yaml > role.yaml
[root@master role]# cat role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role #指定资源类型为Role
metadata:
creationTimestamp: null
name: pod-reader
namespace: default #名字空间为默认
rules:
- apiGroups:
- ""
resources: #可以访问的资源是POD
- pods
verbs: #对该资源可以操作的权限 读访问权限
- get
- watch
- list
# ClusterRole示例
ClusterRole 可以和 Role 相同完成授权。 因为 ClusterRole 属于集群范围,所以它也可以为以下资源授予访问权限:
- 集群范围资源(比如 节点(Node) (opens new window))
- 非资源端点(比如
/healthz
) - 跨名字空间访问的名字空间作用域的资源(如 Pods),比如,你可以使用 ClusterRole 来允许某特定用户执行
kubectl get pods --all-namespaces
下面是一个 ClusterRole 的示例,可用来为任一特定名字空间中的 Secret (opens new window) 授予读访问权限, 或者跨名字空间的访问权限(取决于该角色是如何绑定 (opens new window)的):
[root@master role]# kubectl create clusterrole secret-reader --resource=secrets --verb=get,watch,list --dry-run -o yaml > clusterrole.yaml
[root@master role]# cat clusterrole.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
creationTimestamp: null
name: secret-reader
rules:
- apiGroups:
- ""
resources:
- secrets
verbs:
- get
- watch
- list
# RoleBinding和ClusterRoleBinding
角色绑定(Role Binding)是将角色中定义的权限赋予一个或者一组用户
。 它包含若干 主体(用户、组或服务账户)的列表和对这些主体所获得的角色的引用。 RoleBinding 在指定的名字空间中执行授权,而 ClusterRoleBinding 在集群范围执行授权。
一个 RoleBinding 可以引用同一的名字空间中的任何 Role。 或者,一个 RoleBinding 可以引用某 ClusterRole 并将该 ClusterRole 绑定到 RoleBinding 所在的名字空间。 如果你希望将某 ClusterRole 绑定到集群中所有名字空间,你要使用 ClusterRoleBinding。
# RoleBinding 示例
- 下面的例子中的 RoleBinding 将 "pod-reader" Role 授予在 "default" 名字空间中的用户 "jane"。 这样,用户 "jane" 就具有了读取 "default" 名字空间中 pods 的权限。
RoleBinding 也可以引用 ClusterRole,以将对应 ClusterRole 中定义的访问权限授予 RoleBinding 所在名字空间的资源。
这种引用使得你可以跨整个集群定义一组通用的角色, 之后在多个名字空间中复用。
[root@master role]# kubectl create rolebinding read-pods --namespace=default --user=jane --role=pod-reader --dry-run -o yaml > rolebinding.yaml
[root@master role]# cat rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding #此角色绑定允许‘jane’读取‘default’名字空间中的Pods
metadata:
creationTimestamp: null
name: read-pods
namespace: default
roleRef: #指定某个Role或者是Clusterrole的绑定关系
apiGroup: rbac.authorization.k8s.io
kind: Role #此字段是Role或者clusterRole
name: pod-reader #这是你的Role或者clusterRole的名字匹配
subjects: #你可以指定不止一个subject(主体)
- apiGroup: rbac.authorization.k8s.io
kind: User
name: jane #name是区分大小写的
尽管下面的 RoleBinding 引用的是一个 ClusterRole,"dave"(这里的主体, 区分大小写)只能访问 "development" 名字空间中的 Secrets 对象,因为 RoleBinding 所在的名字空间(由其 metadata 决定)是 "development"。
apiVersion: rbac.authorization.k8s.io/v1
# 此角色绑定使得用户 "dave" 能够读取 "development" 名字空间中的 Secrets
# 你需要一个名为 "secret-reader" 的 ClusterRole
kind: RoleBinding
metadata:
name: read-secrets
# RoleBinding 的名字空间决定了访问权限的授予范围。
# 这里隐含授权仅在 "development" 名字空间内的访问权限。
namespace: development
subjects:
- kind: User
name: dave # 'name' 是区分大小写的
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: secret-reader
apiGroup: rbac.authorization.k8s.io
# ClusterRoleBinding示例
要跨整个集群完成访问权限的授予,你可以使用一个 ClusterRoleBinding。 下面的 ClusterRoleBinding 允许 "manager" 组内的所有用户访问任何名字空间中的 Secrets。
[root@master role]# kubectl create clusterrolebinding read-secrets-global --clusterrole=secret-reader --group=manager --dry-run -o yaml > clusterrolebinding.yaml
[root@master role]# cat clusterrolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding # 此集群角色绑定允许 “manager” 组中的任何人访问任何名字空间中的 secrets
metadata:
creationTimestamp: null
name: read-secrets-global
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: secret-reader
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: Group
name: manager
绑定之后,你不能再修改绑定对象所引用的Role或ClusterRole。试图改变绑定对象的 roleRef 将导致合法性检查错误。如果你想要改变现有绑定对象中 roleRef字段的内容,必须删除重新创建绑定对象。
这种限制有两个主要原因:
- 针对不同角色的绑定是完全不一样的绑定。要求通过删除/重建绑定来更改
roleRef
, 这样可以确保要赋予绑定的所有主体会被授予新的角色(而不是在允许或者不小心修改 了roleRef
的情况下导致所有现有主体未经验证即被授予新角色对应的权限)。 - 将
roleRef
设置为不可以改变,这使得可以为用户授予对现有绑定对象的update
权限, 这样可以让他们管理主体列表,同时不能更改被授予这些主体的角色。
pods
对应名字空间作用域的Pod资源,而 log
是 pods
的子资源。在RBAC角色表达子资源时,使用斜线(/
)来分隔资源和子资源。要允许某主体读取 pods
同时访问这些Pod的 log
子资源
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: pod-and-pod-logs-reader
rules:
- apiGroups: [""]
resources: ["pods", "pods/log"] #pods/log是pod的资源
verbs: ["get", "list"]
#也可以通过 resourceNames 列表按名称引用资源。
#在 HTTP 层面,用来访问 ConfigMap 的资源的名称为 "configmaps"
resources: ["configmaps"]
resourceNames: ["my-configmap"]
verbs: ["update", "get"
# 聚合的 ClusterRole
你可以将若干 ClusterRole 聚合(Aggregate) 起来,形成一个复合的 ClusterRole。 某个控制器作为集群控制面的一部分会监视带有 aggregationRule
的 ClusterRole 对象集合。aggregationRule
为控制器定义一个标签 选择算符 (opens new window)供后者匹配 应该组合到当前 ClusterRole 的 roles
字段中的 ClusterRole 对象。
下面是一个聚合 ClusterRole 的示例:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: monitoring
aggregationRule:
clusterRoleSelectors:
- matchLabels:
rbac.example.com/aggregate-to-monitoring: "true"
rules: [] # 控制面自动填充这里的规则
- 如果你创建一个与某现有聚合 ClusterRole 的标签选择算符匹配的 ClusterRole, 这一变化会触发新的规则被添加到聚合 ClusterRole 的操作。
- 默认的面向用户的角色 (opens new window) 使用 ClusterRole 聚合。 这使得作为集群管理员的你可以为扩展默认规则,包括为定制资源设置规则, 比如通过 CustomResourceDefinitions 或聚合 API 服务器提供的定制资源。
下面的例子中,通过创建一个标签同样为 rbac.example.com/aggregate-to-monitoring: true
的 ClusterRole,新的规则可被添加到 "monitoring" ClusterRole 中。
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: monitoring-endpoints
labels:
rbac.example.com/aggregate-to-monitoring: "true"
# 当你创建 "monitoring-endpoints" ClusterRole 时,
# 下面的规则会被添加到 "monitoring" ClusterRole 中
rules:
- apiGroups: [""]
resources: ["services", "endpoints", "pods"]
verbs: ["get", "list", "watch"]
下面的ClusterRoles让默认角色 "admin"和"edit"拥有管理自定义资源 "CronTabs" 的权限,"view" 角色对 CronTab资源拥有读操作权限。你可以假定CronTab对象在API服务器所看到的URL中被命名为 "crontabs"。
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: aggregate-cron-tabs-edit
labels:
# 添加以下权限到默认角色 "admin" 和 "edit" 中
rbac.authorization.k8s.io/aggregate-to-admin: "true"
rbac.authorization.k8s.io/aggregate-to-edit: "true"
rules:
- apiGroups: ["stable.example.com"]
resources: ["crontabs"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: aggregate-cron-tabs-view
labels:
# 添加以下权限到 "view" 默认角色中
rbac.authorization.k8s.io/aggregate-to-view: "true"
rules:
- apiGroups: ["stable.example.com"]
resources: ["crontabs"]
verbs: ["get", "list", "watch"]
# 面向用户的角色
- 一些默认的 ClusterRole 不是以前缀
system:
开头的。这些是面向用户的角色。 它们包括超级用户(Super-User)角色(cluster-admin
)、 使用 ClusterRoleBinding 在集群范围内完成授权的角色(cluster-status
)、 以及使用 RoleBinding 在特定名字空间中授予的角色(admin
、edit
、view
)。 - 面向用户的 ClusterRole 使用 ClusterRole 聚合 (opens new window)以允许管理员在 这些 ClusterRole 上添加用于定制资源的规则。如果想要添加规则到
admin
、edit
或者view
, 可以创建带有以下一个或多个标签的 ClusterRole:
metadata:
labels:
rbac.authorization.k8s.io/aggregate-to-admin: "true"
rbac.authorization.k8s.io/aggregate-to-edit: "true"
rbac.authorization.k8s.io/aggregate-to-view: "true"
# Pod的安全策略
PodSecurityPolicy 在 Kubernetes v1.21 版本中被弃用,将在 v1.25 中删除。
Pod 安全策略使得对 Pod 创建和更新进行细粒度的权限控制成为可能。
# 什么是Pod安全策略?
Pod 安全策略(Pod Security Policy) 是集群级别的资源,它能够控制 Pod 规约 中与安全性相关的各个方面。 PodSecurityPolicy (opens new window) 对象定义了一组 Pod 运行时必须遵循的条件及相关字段的默认值,只有 Pod 满足这些条件 才会被系统接受。 Pod 安全策略允许管理员控制
Pod 安全策略 由设置和策略组成,它们能够控制 Pod 访问的安全特征。这些设置分为如下三类:
- 基于布尔值控制 :这种类型的字段默认为最严格限制的值。
- 基于被允许的值集合控制 :这种类型的字段会与这组值进行对比,以确认值被允许。
- 基于策略控制 :设置项通过一种策略提供的机制来生成该值,这种机制能够确保指定的值落在被允许的这组值中。
# 启用Pod安全策略
Pod 安全策略实现为一种可选的 准入控制器 (opens new window)。 启用了准入控制器 (opens new window) 即可强制实施 Pod 安全策略,不过如果没有授权认可策略之前即启用准入控制器将导致集群中无法创建任何 Pod。
由于Pod安全策略API(policy/v1beta1/podsecuritypolicy
)是独立于准入控制器 来启用的,对于现有集群而言,建议在启用准入控制器之前先添加策略并对其授权。
# 授权策略
PodSecurityPolicy 资源被创建时,并不执行任何操作。为了使用该资源,需要对 发出请求的用户或者目标 Pod 的 服务账号 (opens new window) 授权,通过允许其对策略执行 use
动词允许其使用该策略。
大多数 Kubernetes Pod 不是由用户直接创建的。相反,这些 Pod 是由 Deployment (opens new window)、 ReplicaSet (opens new window) 或者经由控制器管理器模版化的控制器创建。
赋予控制器访问策略的权限意味着对应控制器所创建的 所有 Pod 都可访问策略。 因此,对策略进行授权的优先方案是为 Pod 的服务账号授予访问权限
# 配置
为运行此示例,配置一个名字空间和一个服务账号。我们将用这个服务账号来 模拟一个非管理员账号的用户。
[root@master pod]# kubectl create namespace psp-example
[root@master pod]# kubectl create serviceaccount -n psp-example fake-user
[root@master pod]# kubectl create rolebinding -n psp-example fake-editor --clusterrole=edit --serviceaccount=psp-example:fake-user
创建两个别名,以更清晰地展示我们所使用的用户账号,同时减少一些键盘输入:
[root@master pod]# alias kubectl-admin='kubectl -n psp-example'
[root@master pod]# alias kubectl-user='kubectl --as=system:serviceaccount:psp-example:fake-user -n psp-example'
# 创建一个策略和一个Pod
在一个文件中定义一个示例的 PodSecurityPolicy 对象。 这里的策略只是用来禁止创建有特权要求的 Pods。
[root@master pod]# cat podsecuritypolicy.yaml
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: example
spec:
privileged: false # Don't allow privileged pods!
seLinux: # 其余部分填写一些必填字段
rule: RunAsAny
supplementalGroups:
rule: RunAsAny
runAsUser:
rule: RunAsAny
fsGroup:
rule: RunAsAny
volumes:
- '*'
使用 kubectl 执行创建操作:
[root@master pod]# kubectl-admin create -f podsecuritypolicy.yaml
现在,作为一个非特权用户,尝试创建一个简单的 Pod:
kubectl-user create -f- <<EOF
apiVersion: v1
kind: Pod
metadata:
name: pause
spec:
containers:
- name: pause
image: k8s.gcr.io/pause
EOF
输出类似于:
Error from server (Forbidden): error when creating "STDIN": pods "pause" is forbidden: unable to validate against any pod security policy: []
尽管 PodSecurityPolicy 被创建,Pod 的服务账号或者 fake-user
用户都没有使用该策略的权限。
[root@master pod]# kubectl-user auth can-i use podsecuritypolicy/example
Warning: resource 'podsecuritypolicies' is not namespace scoped in group 'policy'
no
# 命名空间下Pod配额
Resource Quotas(资源配额,简称quota)是对namespace进行资源配额,限制资源使用的一种策略。
K8S是一个多用户架构,当多用户或者团队共享一个K8S系统时,SA使用quota防止用户(基于namespace的)的资源抢占,定义好资源分配策略。
Quota应用在Namespace上,默认情况下,没有Resource Quota的,需要另外创建Quota,并且每个Namespace最多只能有一个Quota对象。
# 创建一个命名空间
首先创建一个命名空间,这样可以将本次操作中创建的资源与集群其他资源隔离开来。
[root@master pod]# kubectl create namespace quota-pod-example
namespace/quota-pod-example created
# 创建ResourceQuota
创建一个quota限制最大的pod数量是2个
[root@master pod]# kubectl create quota pod-demo --hard=pods=2 --namespace=quota-pod-example --dry-run -o yaml > quota.yaml
[root@master pod]# cat quota.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
creationTimestamp: null
name: pod-demo
namespace: quota-pod-example
spec:
hard:
pods: "2"
status: {}
# 验证该资源限制
这里使用deployment的控制器来创建3个Pod
,此时会发现,deployment的pod被限制成2个pod
[root@master pod]# kubectl create deployment nginx --image=172.25.253.3/library/nginx --replicas=3 --namespace quota-pod-example
deployment.apps/nginx created
[root@master pod]# kubectl get deployments.apps -n quota-pod-example
NAME READY UP-TO-DATE AVAILABLE AGE
nginx 2/3 2 2 11s
# 清理
删除命名空间
[root@master pod]# kubectl delete namespaces quota-pod-example
namespace "quota-pod-example" deleted
# 配置API对象配额
# 创建命名空间
创建一个命名空间以便本例中创建的资源和集群中的其余部分相隔离。
[root@master pod]# kubectl create namespace quota-object-example
namespace/quota-object-example created
# 创建ResourceQuota
下面是一个 ResourceQuota 对象的配置文件
[root@master pod]# kubectl create quota object-quota-demo --namespace quota-object-example --hard=persistentvolumeclaims=1,services.loadbalancers=2,services.nodeports=0 --dry-run -o yaml > quota-api.yaml
[root@master pod]# cat quota-api.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
creationTimestamp: null
name: object-quota-demo
namespace: quota-object-example
spec:
hard:
persistentvolumeclaims: "1"
services.loadbalancers: "2"
services.nodeports: "0"
status: {}
[root@master pod]# kubectl apply -f quota-api.yaml
resourcequota/object-quota-demo created
查看 ResourceQuota 的详细信息:
kubectl get resourcequota object-quota-demo --namespace=quota-object-example --output=yaml
输出结果表明在 quota-object-example 命名空间中,至多只能有一个 PersistentVolumeClaim, 最多两个 LoadBalancer 类型的服务,不能有 NodePort 类型的服务。
status:
hard:
persistentvolumeclaims: "1"
services.loadbalancers: "2"
services.nodeports: "0"
used:
persistentvolumeclaims: "0"
services.loadbalancers: "0"
services.nodeports: "0"
# 创建PersistentVolumeClaim
下面是一个 PersistentVolumeClaim 对象的配置文件:
[root@master pod]# cat pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
namespace: quota-object-example
name: pvc-quota-demo
spec:
storageClassName: manual
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 3Gi
[root@master pod]# kubectl apply -f pvc.yaml
persistentvolumeclaim/pvc-quota-demo created
输出信息表明 PersistentVolumeClaim 存在并且处于 Pending 状态:
[root@master pod]# kubectl get pvc -n quota-object-example
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc-quota-demo Pending manual 6s
# 尝试创建第二个PersistentVolumeClaim
下面是第二个 PersistentVolumeClaim 的配置文件:
[root@master pod]# vim pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
namespace: quota-object-example
name: pvc-quota-demo2
spec:
storageClassName: manual
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 4Gi
尝试创建第二个 PersistentVolumeClaim:
输出信息表明第二个 PersistentVolumeClaim 没有创建成功,因为这会超出命名空间的配额。
[root@master pod]# kubectl apply -f pvc.yaml
Error from server (Forbidden): error when creating "pvc.yaml": persistentvolumeclaims "pvc-quota-demo3" is forbidden: exceeded quota: object-quota-demo, requested: persistentvolumeclaims=1, used: persistentvolumeclaims=1, limited: persistentvolumeclaims=1