创建第一个 pod
创建 nginx pod
编写 yaml 文件
cd ~
vim nginx.yaml
apiVersion: v1
kind: Pod
metadata:
name: my-nginx
labels:
name: my-nginx
spec:
containers:
- name: my-nginx
image: nginx
resources:
limits:
memory: "128Mi"
cpu: "500m"
ports:
- containerPort: 80
创建 pod
kubectl apply -f nginx.yaml
查看 pod
kubectl get po
可以看到
NAME READY STATUS RESTARTS AGE
my-nginx 1/1 Running 0 57s
查看详细信息
kubectl describe pod my-nginx
使用 job 创建 pod
cd ~
vim hellojob.yaml
编写 job ,hello 这个 job 会跑 5 次 pod
apiVersion: batch/v1
kind: Job
metadata:
name: hello
spec:
completions: 5
template:
# 以下是 Pod
spec:
containers:
- name: hello
image: nginx
command: ['sh', '-c', 'echo "Hello, Kubernetes! " && sleep 2']
restartPolicy: OnFailure
创建 job
kubectl create -f hellojob.yaml
kubectl get job
kubectl get po
kubectl get po -w
kubectl get po -w
kubectl delete job hello
为容器的生命周期事件设置处理函数
创建一个包含一个容器的 Pod ,该容器为 postStart 和 preStop 事件提供对应的处理函数
在从节点 node1 :
cd ~
vim lifecycle.yaml
apiVersion: v1
kind: Pod
metadata:
name: lifecycle-demo
spec:
containers:
- name: lifecycle-demo-container
image: nginx
lifecycle:
postStart:
exec:
command: ["/bin/sh", "-c", "echo Hello from the postStart handler > /usr/share/message"]
preStop:
exec:
command: ["/bin/sh","-c","nginx -s quit; while killall -0 nginx; do sleep 1; done"]
kubectl create -f lifecycle.yaml
kubectl get po
kubectl exec -it lifecycle-demo sh
ls
cat /usr/share/message
可以看到
Hello from the postStart handler
创建包含 Init 容器的 Pod
Init 容器
每个 pod 中可以包含多个容器,应用运行在这些容器里面,同时 Pod 也可以有一个或多个先于应用容器启动的 Init 容器
Init 容器与普通的容器非常像,除了如下两点:
它们总是运行到完成
每个都必须在下一个启动之前成功完成
如果 pod 的 Init 容器失败,Kubernetes 会不断重启该 pod ,知道 Init 容器成功为止。如果 pod 对应的 restartPolicy 值为 Never ,Kubernetes 不会重新启动 pod
与普通容器的不同之处
Init 容器支持应用容器的全部属性和特性,包括资源限制、数据卷和安全设置。
同时 Init 容器不支持 lifecycle, livenessProbe, readinessProbe 和 startupProbe, 因为它们必须在 pod 就绪之前运行完成。
定义一个具有两个 Init 容器的 pod 。第一个等待 myservice 启动,第二个等待 mydb 启动。一旦这两个 Init 容器都启动完成,pod 将启动 spec 节中的应用容器。
cd ~
vim initPod.yaml
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:
app: myapp
spec:
containers:
- name: myapp-container
image: busybox:1.28
command: ['sh', '-c', 'date && sleep 3600']
initContainers:
- name: init-container
image: busybox:1.28
command: ['sh', '-c', "date && sleep 10"]
启动 pod
kubectl apply -f initPod.yaml
kubectl get po
查看 pod 日志
kubectl logs myapp-pod
Thu Mar 18 08:52:08 UTC 2021
查看 pod 内 init 容器的日志
kubectl logs myapp-pod -c init-container
Thu Mar 18 08:51:57 UTC 2021
用探针检查 pod 的健康性
探针是 kubelet 对容器执行的定期诊断,监测 pod 里面的容器是不是正常启动了。kubelet 调用由容器实现的 Handler 进行诊断,Handler 的类型有三种:
- ExecAction:在容器内执行指定命令。如果命令退出时返回码为 0 则认为诊断成功
- TCPSocketAction:对容器的 ip 地址上的指定端口进行 TCP 检查,如果端口打开,则认为诊断成功。
- HTTPGetAction:对容器的 ip 地址上的指定端口的路径执行 HTTP Get 请求。如果响应的状态码大于等于 200 并且小于 400,则认为诊断是成功的。
每次诊断都将获得以下三种结果之一:
- Success(成功):容器通过了诊断
- Failure(失败):容器未通过诊断
- Unknown(未知):诊断失败,因此不会采取任何行动
什么时候使用探针
对于所包含的容器需要比较长的时间才能启动就绪的 pod ,启动探针是非常有用的。
HTTP 请求探针
cd ~
vim liveness.yaml
apiVersion: v1
kind: Pod
metadata:
labels:
test: liveness
name: liveness-http
spec:
containers:
- name: liveness
image: mirrorgooglecontainers/liveness
args:
- /server
livenessProbe:
httpGet:
path: /healthz
port: 8080
httpHeaders:
- name: Custom-Header
value: Awesome
initialDelaySeconds: 3
periodSeconds: 3
上面官方提供的 liveness 容器,10秒之内,服务会给 /healthz 请求返回 200,10秒之后,会返回 500:
http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
duration := time.Now().Sub(started)
if duration.Seconds() > 10 {
w.WriteHeader(500)
w.Write([]byte(fmt.Sprintf("error: %v", duration.Seconds())))
} else {
w.WriteHeader(200)
w.Write([]byte("ok"))
}
})
initialDelaySeconds: 3
字段告诉 kubelet 在执行第一次探测之前应该等待 3 秒
periodSeconds: 3
字段指定了 kubelet 每隔 3 秒执行一次存活探测
启动 pod
kubectl apply -f liveness.yaml
观察 pod
kubectl get po -w
kubelet 在容器启动 3 秒后,开始健康检测,启动超过 10 秒之后,健康检查会失败,kubelet 会重启容器。
为容器设置启动时要执行的命令和参数
创建 pod 时,可以为其下的容器设置启动时要执行的命令及参数。通过 command 字段设置命令,通过 args 字段设置命令的参数。
cd ~
vim args.yaml
apiVersion: v1
kind: Pod
metadata:
name: command-demo
labels:
purpose: demonstrate-command
spec:
containers:
- name: command-demo-container
image: debian
command: ["printenv"]
args: ["HOSTNAME", "KUBERNETES_PORT"]
restartPolicy: OnFailure
以上,设置了一个命令,两个参数。命令会打印两个参数。
restartPolicy: OnFailure
字段的意思是:失败了才会重启
创建 pod
kubectl apply -f args.yaml
kubectl get po
kubectl logs command-demo
可以看到,打印了两个参数:
command-demo
tcp://10.1.0.1:443
使用环境变量设置参数
kubectl delete -f args.yaml
vim args.yaml
apiVersion: v1
kind: Pod
metadata:
name: command-demo
labels:
purpose: demonstrate-command
spec:
containers:
- name: command-demo-container
image: debian
env:
- name: MESSAGE
value: "hello world"
command: ["/bin/echo"]
args: ["$(MESSAGE)"]
restartPolicy: OnFailure
kubectl apply -f args.yaml
kubectl get po -w
kubectl logs command-demo
可以看到:
hello world
为容器定义相互依赖的环境变量
可以为运行在 pod 中的容器设置相互依赖的环境变量
vim dependency-var.yaml
apiVersion: v1
kind: Pod
metadata:
name: dependent-envars-demo
spec:
containers:
- name: dependent-envars-demo
args:
- printf UNCHANGED_REFERENCE=$UNCHANGED_REFERENCE'\n'; printf SERVICE_ADDRESS=$SERVICE_ADDRESS'\n';printf ESCAPED_REFERENCE=$ESCAPED_REFERENCE'\n';
command:
- sh
- -c
image: busybox
env:
- name: SERVICE_PORT
value: "80"
- name: SERVICE_IP
value: "192.168.190.131"
- name: UNCHANGED_REFERENCE
value: "$(PROTOCOL)://$(SERVICE_IP):$(SERVICE_PORT)"
- name: PROTOCOL
value: "https"
- name: SERVICE_ADDRESS
value: "$(PROTOCOL)://$(SERVICE_IP):$(SERVICE_PORT)"
- name: ESCAPED_REFERENCE
value: "$$(PROTOCOL)://$(SERVICE_IP):$(SERVICE_PORT)"
kubectl apply -f dependency-var.yaml
kubectl get po
kubectl logs dependent-envars-demo
可以看到:
UNCHANGED_REFERENCE=$(PROTOCOL)://192.168.190.131:80
SERVICE_ADDRESS=https://192.168.190.131:80
ESCAPED_REFERENCE=$(PROTOCOL)://192.168.190.131:80
在变量 PROTOCOL
定义之前打印 $(PROTOCOL)
,会打印:$(PROTOCOL)
打印 $$(PROTOCOL)
会打印:$(PROTOCOL)
为容器和 Pods 分配 CPU 资源
创建一个命名空间,以便将本次练习中创建的资源与集群的其余部分资源隔离
kubectl create namespace cpu-example
为容器指定 CPU 请求,使用字段:
resources: requests
指定 CPU 限制,使用字段:
resources: limits
创建一个 pod , pod 内有一个容器,容器请求 0.5 个 CPU ,并且限制最多使用 1 个 CPU
vim cpu-request-limit.yaml
apiVersion: v1
kind: Pod
metadata:
name: cpu-demo
namespace: cpu-example
spec:
containers:
- name: cpu-demo-ctr
image: nginx
resources:
limits:
cpu: "1"
requests:
cpu: "0.5"
args:
- -cpus
- "2"
arg
提供了容器启动时的参数,优先级高于默认的参数,这里 -cpu
"2"
告诉容器尝试使用 2 个 CPU ,但是结果并不会使用 2 个 CPU ,因为 resources: limits
限制了 CPU 个数是 1 。
安装 work2
克隆 work1 虚拟机,链接克隆或者完整克隆都可以,命名为 work2 ,克隆完成后,启动 work2 虚拟机
-
修改 ip :
vi /etc/sysconfig/network-scripts/ifcfg-ens33
IPADDR=192.168.190.133
重启网络:
systemctl restart network
-
修改 hostname
hostnamectl set-hostname node2
- 查看 hostname :
hostname
- 查看 hostname :
-
重置 kubeadm
kubeadm reset
- 输入:
y
-
配置端口转发:
echo 1 > /proc/sys/net/ipv4/ip_forward echo 1 > /proc/sys/net/bridge/bridge-nf-call-iptables
-
将 master 节点的 admin.conf 复制到 work2 节点,在 master 节点执行:
scp /etc/kubernetes/admin.conf root@node2:/etc/kubernetes/
-
清理环境
systemctl stop kubelet && systemctl stop docker && rm -rf /var/lib/cni/ && rm -rf /var/lib/kubelet/* && rm -rf /etc/cni/ && ifconfig flannel.1 down && ifconfig docker0 down && ip link delete flannel.1
-
启动 docker 和 kubernetes
systemctl start docker && systemctl start kubelet
-
在 master 节点生成 token 给 work2 节点,在 master 节点执行:
kubeadm token create --print-join-command
-
生成的 token
kubeadm join 192.168.190.131:6443 --token hjyu47.u4ezlgt0yuw8jwrf --discovery-token-ca-cert-hash sha256:1271d25165cffe9623cc85980a5ac950eba7ff066149b590a509d3e09594a09d
复制到 work2 节点
等待半分钟至1分钟,执行:
kubectl get nodes
-
可以看到:
NAME STATUS ROLES AGE VERSION master Ready master 30h v1.19.3 node1 Ready
27h v1.19.3 node2 Ready 78s v1.19.3
用节点亲和性把 Pods 分配到节点
列出集群中的节点和节点的标签
kubectl get nodes --show-labels
给节点添加标签,这里区分一下:work1 是虚拟机的名字;node1 是节点的名字
kubectl label nodes disktype=ssd
看一下这个 pod ,通过 affinity
指定了 disktype
是 ssd
的节点
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: disktype
operator: In
values:
- ssd
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
可以通过 kubectl get pods --output=wide
查看 pod 运行在哪个节点
将 ConfigMap 中的兼职对配置为容器环境变量
vim config.yaml
下面的内容,包含 configmap 和 pod
# configmap
apiVersion: v1
kind: ConfigMap
metadata:
# configmap 的名字
name: my-db-config
# configmap 的数据
data:
db-url: localhost
---
# pod
apiVersion: v1
kind: Pod
metadata:
name: myapp
labels:
name: myapp
spec:
containers:
- name: myapp
image: busybox
# 命令:写入环境变量
command: ["sh","-c","env"]
# 环境变量来自于:
envFrom:
# 指定 configmap
- configMapRef:
name: my-db-config
resources:
limits:
memory: "128Mi"
cpu: "500m"
一次创建 configmap 和 pod 两个对象
kubectl create -f configmap.yaml
可以看到:
configmap/my-db-config created
pod/myapp created
查看日志
kubectl logs myapp
可以看到 db-url=localhost
已经写入环境变量
容器 Root 用户 VS privileged
大多数容器默认以 root 身份运行,不过为了安全,默认的 root 用户实际上是映射的 root 用户,并不具备 root 用户的全部功能,如果需要更大的权限,需要使用特权用户,在运行容器的时候,加上 --privileged
参数。
[root@node1 ~]# docker run -it busybox sh
/ # whoami
root
/ # id
uid=0(root) gid=0(root) groups=10(wheel)
/ # hostname
af29e1748ec7
/ # sysctl kernel.hostname=Attacker
sysctl: error setting key 'kernel.hostname': Read-only file system
/ # exit
[root@node1 ~]#
上面的例子,以默认的 root 用户运行容器,在修改 hostname 的时候,提示没有权限。
下面的例子,在运行容器的时候,加上 --privileged
参数,以特权用户运行
[root@node1 ~]# docker run -it --privileged busybox sh
/ # whoami
root
/ # id
uid=0(root) gid=0(root) groups=10(wheel)
/ # hostname
c21cf0211dc1
/ # sysctl kernel.hostname=Attacker
kernel.hostname = Attacker
/ # hostname
Attacker
/ # exit
[root@node1 ~]#
可以看到,hostname 修改成功了。
Kubernetes 通过 Security Context
提供了相同的功能
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx
securityContext:
privileged: true
为 Pod 创建非 Root 用户运行
创建一个设置 Security Context
的 pod
vim security.yaml
apiVersion: v1
kind: Pod
metadata:
name: security-context-demo
spec:
securityContext:
# 容器内所有进程都以 1000 这个用户id来运行
runAsUser: 1000
# 进程都以主组id 3000 来运行,所有创建的文件,也划归用户1000和主组3000;如果忽略此字段,则容器的主组id将是root(0)
runAsGroup: 3000
# 容器中所有进程也会是附组id2000的一部分。卷/data/demo及在该卷中创建的任何文件的属主都会是组id 2000
fsGroup: 2000
# 在宿主机上挂载一个空目录,存放这个 pod 里面的内容
volumes:
- name: sec-ctx-vol
emptyDir: {}
containers:
- name: sec-ctx-demo
image: busybox
command: [ "sh", "-c", "sleep 1h" ]
# 引用挂载的目录
volumeMounts:
- name: sec-ctx-vol
mountPath: /data/demo
# 是否允许越出权限
securityContext:
allowPrivilegeEscalation: false
kubectl create -f security.yaml
[root@node1 ~]# kubectl exec -it security-context-demo sh
/ $ id
uid=1000 gid=3000 groups=2000
/ $ ps
PID USER TIME COMMAND
1 1000 0:00 sleep 1h
6 1000 0:00 sh
12 1000 0:00 ps
/ $ ls
bin data dev etc home proc root sys tmp usr var
/ $ cd data/demo/
/data/demo $ ls -l
total 0
drwxrwsrwx 2 root 2000 6 Mar 19 03:35 .
/data/demo $ exit
[root@node1 ~]#