k8s in action—Pod

本章用到的三个 YAML 描述文件如下:

kubia-manual.yaml

1
2
3
4
5
6
7
8
9
10
11
apiVersion: v1
kind: Pod
metadata:
name: kubia-manual
spec:
containers:
- image: luksa/kubia
name: kubia
ports:
- containerPort: 8080
protocol: TCP

kubia-manual-with-labels.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: v1
kind: Pod
metadata:
name: kubia-manual-v2
labels:
creation_mrthod: manual
env: prod
spec:
containers:
- image: luksa/kubia
name: kubia
ports:
- containerPort: 8080
protocol: TCP

kubia-gpu.yaml

1
2
3
4
5
6
7
8
9
10
apiVersion: v1
kind: Pod
metadata:
name: kubia-gpu
spec:
nodeSelector:
gpu: "true"
containers:
- image: luksa/kubia
name: kubia

apiVersion 描述文件遵循什么版本的 Kubernetes API;
kind 指定资源类型,这里为 pod;
metadata 中的字段描述 pod 名称、标签、注解;
spec 中的字段描述创建容器所需要的镜像、容器名称、监听端口等。

创建 pod

使用 kubectl create -f 命令从 YAML 文件创建 pod

1
kubectl create -f kubia-manual.yaml

得到运行中 pod 的完整定义

1
2
3
4
5
# YAML 格式
kubectl get pod kubia-manual -o yaml

# JSON 格式
kubectl get pod kubia-manual -o json

查看 pod

1
kubectl get pods

查看日志

1
2
3
4
kubectl logs kubia-manual

# 获取多容器 pod 的日志时指定容器名称
kubectl logs kubia-manual -c kubia

将本机的 8888 端口转发至 kubia-manual pod 的 8080 端口

1
kubectl port-forward kubia-manual 8888:8080

使用标签组织 pod

标签时可以附加到资源的任意键值对,用以选择具有该确切标签的资源。

创建带标签的 pod

1
kubectl create -f kubia-manual-with-labels.yaml

基于标签的 pod 操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 显示 pod 标签
kubectl get pods --show-labels

# 只显示 env 标签
kubectl get pods -L env

# 为指定的 pod 资源添加新标签
kubectl label pod kubia-manual env=debug

# 修改标签
kubectl label pod kubia-manual env=online --overwrite=true

# - 号删除标签
kubectl label pod kubia-manual env-

使用标签选择器列出 pod

筛选指定值的 pod

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 筛选 env 为 debug 的 pods
kubectl get pods -l env=debug

# 筛选 creation_method 不为 manual 的 pods
kubectl get pods -l creation_method!=manual

# 筛选不含 env 标签的 pods
kubectl get pods -l '!env'

# in 筛选
kubectl get pods -l 'env in (debug)'

# not in 筛选
kubectl get pods -l 'env notin (debug,online)'

使用标签和选择器约束 pod 调度

使用标签分类工作节点

1
2
kubectl label node gke-kubia gpu=true
kubectl get nodes -l gpu=true

在 kubia-gpu.yaml 中的 spec 添加了 nodeSelector 字段。创建该 pod 时,调度器将只在包含标签 gpu=true 的节点中选择。

注解 pod

注解也是键值对,但与标签不同,不用作标识,用作资源说明,注解可以包含相对更多的数据。

使用 kubectl annotate 添加注解

1
kubectl annotate pod kubia-manual mycompany.com/someannotation="foo bar"

查看注解

1
kubectl describe pod kubia-manual

命名空间

标签会导致资源重叠,可用 namespace 将对象分配到集群级别的隔离区域,相当于多租户的概念,以 namespace 为操作单位。

获取所有 namespace

1
kubectl get ns

获取属于 kube-system 命名空间的 pod

1
kubectl get pods -n kube-system

创建 namespace 资源

1
2
3
4
apiVersion: v1
kind: Namespace
metadata:
name: custom-namespace

创建资源时在 metadata.namespace 中指定资源的命名空间

1
2
3
4
5
6
apiVersion: v1
kind: Pod
metadata:
name: kubia-manual
namespace: custom-namespace
...

使用 kubectl create 命令创建资源时指定命名空间

1
kubectl create -f kubia-manual.yaml -n custom-namespace

切换命名空间

1
kubectl config set-context $(kubectl config current-context) --namespace custom-namespace

删除 pod

删除原理:向 pod 所有容器进程定期发送 SIGTERM 信号,使其正常关闭。如果没有及时关闭,则发送 SIGKILL 强制终止。进程需要正确处理信号,如 Go 注册捕捉信号 signal.Notify() 后 select 监听该 channel。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 按名称删除 pod
kubectl delete po kubia-gpu

# 删除指定标签的 pod
kubectl delete po -l env=debug

# 通过删除整个命名空间来删除 pod
kubectl delete ns custom-namespace

# 删除当前命名空间下的所有 pod(慎用)
kubectl delete pod --all

# 删除当前命名空间的所有资源(慎用)
kubectl delete all --all

CentOS7.6部署k8s

两台 2 核 CPU、2G 内存的阿里云服务器,一台 master 节点,一台 node 节点。

准备工作

关闭防火墙

1
2
systemctl stop firewalld
systemctl disable firewalld

禁用 swap 分区

1
swapoff -a && sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab

禁用 SELinux

1
setenforce 0 && sed -i 's/^SELINUX=.*/SELINUX=disabled/' /etc/selinux/config

时间同步

1
2
systemctl start chronyd
systemctl enable chronyd

重新设置主机名

1
2
3
4
5
6
7
8
9
10
# master 节点
hostnamectl set-hostname master

# node 节点
hostnamectl set-hostname node

vi /etc/hosts
# 添加 ip:
8.130.22.97 master
8.130.23.131 node

将桥接的 IPv4 以及 IPv6 的流量串通

1
2
3
4
5
6
cat >/etc/sysctl.d/k8s.conf << EOF
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF

sysctl --system

配置 ipvs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sudo yum install -y yum-utils
yum install ipset ipvsadmin -y

cat <<EOF> /etc/sysconfig/modules/ipvs.modules
#!/bin/bash
modprobe -- ip_vs
modprobe -- ip_vs_rr
modprobe -- ip_vs_wrr
modprobe -- ip_vs_sh
modprobe -- nf_conntrack_ipv4
EOF

chmod +x /etc/sysconfig/modules/ipvs.modules
/bin/bash /etc/sysconfig/modules/ipvs.modules
lsmod | grep -e ip_vs -e nf_conntrack_ipv4

安装 docker

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

yum install docker-ce-19.03.2 docker-ce-cli-19.03.2 containerd.io-1.4.4 -y

# 使用 systemd 代替 cgroupfs,配置仓库镜像地址
mkdir /etc/docker
vi /etc/docker/daemon.json
# 添加:
{
"registry-mirrors": ["https://q2hy3fzi.mirror.aliyuncs.com"],
"exec-opts": ["native.cgroupdriver=systemd"]
}

# 启动 docker
systemctl start docker
systemctl enable docker --now

安装 k8s

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
cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=http://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=0
repo_gpgcheck=0
gpgkey=http://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg
http://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
exclude=kubelet kubeadm kubectl
EOF

yum install -y kubelet-1.23.1 kubeadm-1.23.1 kubectl-1.23.1 --disableexcludes=kubernetes

vi /etc/sysconfig/kubelet
# 添加:
KUBELET_CGROUP_ARGS="--cgroup-driver=systemd"
KUBE_PROXY_MODE="ipvs"

systemctl enable --now kubelet

# 组件下载脚本
sudo tee ./images.sh <<-'EOF'
#!/bin/bash
images=(
kube-apiserver:v1.23.1
kube-proxy:v1.23.1
kube-controller-manager:v1.23.1
kube-scheduler:v1.23.1
coredns:1.7.5
etcd:3.4.13-0
pause:3.2
kubernetes-dashboard-amd64:v1.10.0
heapster-amd64:v1.5.4
heapster-grafana-amd64:v5.0.4
heapster-influxdb-amd64:v1.5.2
pause-amd64:3.1
)
for imageName in ${images[@]} ; do
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/$imageName
docker tag registry.cn-hangzhou.aliyuncs.com/google_containers/$imageName k8s.gcr.io/$imageName
done
EOF
# 执行脚本
chmod +x ./images.sh && ./images.sh

部署 k8s master

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
kubeadm init \
--kubernetes-version v1.23.1 \
--control-plane-endpoint "master:6443" \
--upload-certs \
--image-repository registry.aliyuncs.com/google_containers \
--pod-network-cidr=10.244.0.0/16

# 将执行成功后的命令拷贝过来
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

# kubectl
kubectl get node

# 安装插件
curl https://docs.projectcalico.org/manifests/calico.yaml -O
kubectl apply -f calico.yaml

# 查看 node,状态为 ready
kubectl get node

如果安装失败,需要 reset:

1
2
kubeadm reset
rm -rf $HOME/.kube

部署 k8s node

需要执行在 kubeadm init 输出的 kubeadm join 命令:

1
2
3
kubeadm join master:6443 --token hkakru.rnv32cvzw2alodkw \
--discovery-token-ca-cert-hash sha256:49257352b5a320c40785df0b6cc5534e7ae0b6cc758023b52abc0da4b5e9890d \
--control-plane --certificate-key bd218950889df9e03586f4df549a1ec955715d75fb9e576ec75f3287c0004063

部署 dashboard

1
2
3
4
5
6
7
kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.3.1/aio/deploy/recommended.yaml

kubectl edit svc kubernetes-dashboard -n kubernetes-dashboard
# type: ClusterIP 改为 type: NodePort

# 查看 dashboard 端口
kubectl get svc -A | grep kubernetes-dashboard

通过 https://集群任意IP:端口 来访问,这里可以是 https://8.130.22.97:31323。

添加用户和绑定角色

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
vi dash.yaml
# 添加:
apiVersion: v1
kind: ServiceAccount
metadata:
name: admin-user
namespace: kubernetes-dashboard

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: admin-user
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: admin-user
namespace: kubernetes-dashboard

生成登录 token

1
kubectl -n kubernetes-dashboard describe secret $(kubectl -n kubernetes-dashboard get secret | grep admin-user | awk '{print $1}')

将输出的 token 输入到网站中,就可以看到管理界面了。

Docker 简述

什么是容器

当一个应用程序仅由较少数量的大组件构成时,完全可以接受给每个组件分配专用的虚拟机,以及通过给每个组件提供自己的操作系统实例来隔离它们的环境。但是当这些组件开始变小且数量开始增长时,如果你不想浪费硬件资源,又想持续压低硬件成本,那就不能给每个组件配置一个虚拟机了。

容器类似与虚拟机,但开销小很多。一个容器里运行的进程实际上运行在宿主机的操作系统上,就像所有其他进程一样,不像虚拟机,进程是运行在不同的操作系统上的。但在容器里的进程仍然是和其他进程隔离的。对于容器内进程本身而言,就好像是在机器和操作系统上运行的唯一一个进程。

容器隔离机制

容器实现隔离有两个机制:第一个是 Linux 命名空间,它使每个进程只看到它自己的系统视图;第二个是 cgroups,它限制了进程能使用的资源量。

用 Linux 命名空间隔离进程

每个 Linux 系统最初只有一个命名空间,所有的系统资源都属于这一个命名空间。一个进程在一个命名空间下运行,进程将只能看到同一个命名空间下的资源。通过 clone 函数可以在创建新进程的同时创建 namespace:

1
int pid = clone(main_function, stack_size, CLONE_NEWPID | SIGCHLD, NULL);

在这里指定 CLONE_NEWPID 参数,这样新创建的进程,就会看到一个全新的进程空间。而此时这个新的进程,也就变成了 PID=1 的进程。

与 namespace 相关的 flag 参数有 CLONE_NEWNS、CLONE_NEWUTS、CLONE_NEWIPC、CLONE_NEWPID、CLONE_NEWNET 和 CLONE_NEWUSER,对应 Mount(mnt)、UTS、Inter-process communication(ipc)、Process ID(pid)、Network(net)、User ID(user) 这六种类型的命名空间。

用 cgroups 限制系统的可用资源

cgroups 是一个 Linux 内核功能,它用来限制一个进程或者一组进程的资源使用。一个进程的资源(CPU、内存、网络带宽等)使用量不能超过被分配的量。

cgroups 通过 cgroupfs 提供接口,cgroupfs 默认情况下挂载在 /sys/fs/cgroup 目录。

cgroups 限制 CPU、内存的简单操作方法:

1
2
3
4
5
6
7
8
9
# 创建一个新的目录,也就是创建了一个新的 cgroup 
mkdir /sys/fs/cgroup/cpuset/demo

# 配置这个 cgroup 的资源配额,限制这个 cgroup 的进程只能在 0 号 CPU 上运行,并且只能在 0 号内存节点分配内存
echo 0 > /sys/fs/cgroup/cpuset/demo/cpuset.cpus
echo 0 > /sys/fs/cgroup/cpuset/demo/cpuset.mems

# 将进程 id 写进 tasks 文件,即整个进程移动到 cgroup 中,cgroup 开始起作用
echo > /sys/fs/cgroup/cpuset/demo/tasks

Docker 容器介绍

Docker 是第一个使容器在不同机器之间移植的系统。Docker 有三个概念:

  • 镜像 —— Docker 镜像里包含了打包的应用程序及其所依赖的环境。
  • 镜像仓库 —— Docker 镜像仓库用于存放 Docker 镜像。
  • 容器 —— 一个运行中的容器是一个运行在 Docker 主机上的进程,但它和主机以及所有运行在主机上的其他进程都是隔离的。

Docker 推荐将容器运行时的 cgroup driver 更改为 systemd,systemd 限制 CPU、内存的简单操作方法:

1
2
# 限制 CPU 占用为 0.1 个 CPU,内存为 200 MB
systemctl set-property xxxx.service CPUShares=100 MemoryLimit=200M

systemd 相对 cgroupfs 更加简单,目前主流 Linux 发行版中 systemd 是系统自带的 cgroup 管理器,系统初始化就存在的,和 cgroups 联系紧密。

如果 Docker 的 cgroup driver 是 cgroupfs,就会存在两个控制管理器,对于该服务器上启动的容器使用的是 cgroupfs,而对于其他 systemd 管理的进程使用的是 systemd,这样在服务器资源负载高的情况下可能会变的不稳定。