1. 背景
在https://blog.51cto.com/u_15327484/7969816文章中,介绍了kubernets启动容器的基本流程。在对k8s有一定了解后,可以尝试部署一个集群,后续可以可以基于这个集群进行一些实践,从而有一定产出。一般公司都有kubernets容器管理平台,无需手动创建k8s集群,本次实践仅为了后续对大数据上云方案进行探索性实践。
2. K8S集群规划
系统:debian 9 k8s版本:v1.24.2
机器 | 角色 |
---|---|
gdc-k8smaster01-taylor | master |
gdc-k8snode01-taylor | node |
gdc-k8snode02-taylor | node |
gdc-k8snode03-taylor | node |
gdc-k8snode04-taylor | node |
3. 准备阶段
备注:后续所有操作都基于root用户。
3.1 关闭swap分区
Swap分区介绍:SWAP是操作系统虚拟出来的一部分内存地址,它的物理存储元件是磁盘。在备份数据或恢复数据时,文件系统会向Linux系统请求大量的内存作为cache。在物理内存使用殆尽时候,为了确保程序运行,往往会将另外的一些占用物理内存地址空间的程序映射到swap分区上。
swap分区优点:当内存不足时候,可以将一部分交换出去,不会触发oom-killer。跑得慢总比不能跑好。
swap分区缺点:当进行内存交换时,往磁盘写内存数据,会触发高IO,同时会降低系统的性能。对于我们隔离做的不好的时候,会影响到其他应用的性能。
对重要服务的影响:当由于JVM内存爆满导致Linux使用swap交换内存时,这时候JVM触发GC,GC扫描所有内存,这时候需要访问磁盘,所以相比物理内存,它的速度肯定慢的令人发指,GC停顿的时间一定会非常非常恐怖,造成Java服务基本不可用。因此对于重要的服务,不建议开启swap分区。
关闭swap分区方法:
- 设置/proc/sys/vm/swappiness=0。表示最大限度使用物理内存,然后才是 swap空间。swappiness值越大,对swap分区使用越积极。
- swapoff -a临时关闭,swapon -a临时开启,swapon --show查看。
- vim /etc/fstab,注释LABEL=swap none swap sw 0 0行,重启机器后永久关闭swap。注意:首先要保证内存剩余要大于等于swap使用量,否则会报Cannot allocate memory!swap分区一旦释放,所有存放在swap分区的文件都会转存到物理内存上,可能会引发系统IO或者其他问题。
对于线上的机器,一般设置/proc/sys/vm/swappiness=0,保留4GB swap分区即可。
也可以关闭swap,如图,有2GB swap分区:
root@gdc-k8snode04-taylor:~# free
total used free shared buff/cache available
Mem: 8179236 373536 4175780 84516 3629920 7415220
Swap: 2097148 0 2097148
关闭分区后重启,swap分区大小归0:
ngadm@gdc-k8snode04-taylor:~$ free
total used free shared buff/cache available
Mem: 8179244 263400 7682808 10560 233036 7671028
Swap: 0 0 0
3.2 linux启动netfilter模块
linux收发包流程是一个贼复杂的过程,为了让用户可以在收发包的过程中对数据包进行修改或过滤,linux从2.4版本开始引入了netfilter子系统,在收发包的数据路径中的关键节点上提供扩展(hook)点,这些扩展点上可以编写扩展函数对数据包进行过滤和修改,扩展点一共有五个点,主要有:
PREROUTING
,数据包刚到达时会经过这个点,通常用来完成DNAT的功能。INPUT
,数据包要进入本机的传输层时会经过这个点,通常用来完成防火墙入站检测。FORWARD
,数据包要通过本机转发时会经过这个点,通常用来完成防火墙转发过滤。OUTPUT
,从本机的数据包要出去的时候会经过这个点,通常用来做DNAT和防火墙出站检测。POSTROUTING
,数据包离开本机前会经过这个点,通常用来做SNAT。
对于同节点的两个pod,它们通过二层网络设备网桥相连。pod1通过iptables conntrack(三层转发)请求pod2,当pod2返回时,发现请求的pod在同一网桥上,直接通过网桥(二层转发)返回。由于返回数据没有原路返回(三层转发),客户端认为和服务端不在一个频道,造成DNS 解析失败。
解决方法:由于数据的转发都基于linux的netfilter模块,因此可以加载netfilter,设置bridge-nf-call-iptables
这个内核参数 (置为 1),表示 bridge 设备在二层转发时也去调用 iptables 配置的三层规则 (包含 conntrack)。操作如下:
#linux添加netfilter模块:
modprobe br_netfilter
#查看linux是否加载了netfilter模块:
lsmod | grep br_netfilter
#修改内核参数,启动netfilter的ip转发功能:
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
br_netfilter
EOF
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_nonlocal_bind = 1
net.ipv4.ip_forward = 1
vm.swappiness=0
vm.max_map_count=655360
fs.file-max=1000000
net.ipv4.neigh.default.gc_thresh1=1024
net.ipv4.neigh.default.gc_thresh2=4096
net.ipv4.neigh.default.gc_thresh3=8192
net.core.netdev_max_backlog=10000
fs.inotify.max_user_instances=524288
fs.inotify.max_user_watches=524288
EOF
#使上述配置生效
sudo sysctl --system /etc/sysctl.d/k8s.conf
3.3 linux启动ipvs模块
在 Kubernetes 集群中,每个 Node 运行一个 kube-proxy 进程。kube-proxy 负责为 Service 实现了一种 VIP(虚拟 IP)的形式。Kubernetes v1.8 添加了 ipvs 代理模式。
ipvs是工作在内核态的4层负载均衡,基于内核底层netfilter实现,netfilter主要通过各个链的钩子实现包处理和转发。ipvs由ipvsadm提供简单的CLI接口进行ipvs配置。由于ipvs工作在内核态,只处理四层协议,因此只能基于路由或者NAT进行数据转发,可以把ipvs当作一个特殊的路由器网关,这个网关可以根据一定的算法自动选择下一跳。
#启动ipvs模块:
modprobe -- ip_vs
modprobe -- ip_vs_rr
modprobe -- ip_vs_wrr
modprobe -- ip_vs_sh
modprobe -- nf_conntrack_ipv4
#查看ipvs是否启动:
lsmod | grep -e ip_vs -e nf_conntrack_ipv4
#安装ipvsadm工具:
apt install -y ipvsadm ipset
3.4 安装docker
#首先,更新现有的包列表:
sudo apt update
#接下来,安装一些允许apt使用包通过HTTPS的必备软件包:
sudo apt install apt-transport-https ca-certificates curl gnupg2 software-properties-common
#然后将官方Docker存储库的GPG密钥添加到系统:
curl -fsSL <https://download.docker.com/linux/debian/gpg> | sudo apt-key add -
#将Docker存储库添加到APT源:
sudo add-apt-repository "deb [arch=amd64] <https://download.docker.com/linux/debian> $(lsb_release -cs) stable"
#接下来,使用新添加的repo中的Docker包更新包数据库:
sudo apt update
#最后安装docker:
apt install -y docker-ce
#注意,apt源的目录为`/etc/apt/sources.list`。
#修改docker容器参数cgroupdriver为systemd,和k8s保持一致:
#创建 /etc/docker 目录
mkdir /etc/docker
cat > /etc/docker/daemon.json <<EOF
{
"exec-opts": ["native.cgroupdriver=systemd"],
"log-driver": "json-file",
"log-opts": {
"max-size": "100m"
}
}
EOF
#配置systemctl启动方式,配置开机自启动:
****mkdir -p /etc/systemd/system/docker.service.d
# 重启docker服务
systemctl daemon-reload && systemctl restart docker && systemctl enable docker
3.5 安装kubeadm工具
kubeadm是一个工具,用于安装k8s集群。
#第一步:更新 apt 包索引并安装使用 Kubernetes apt 仓库所需要的包:
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl
#第二步:下载 Google Cloud 公开签名秘钥:
sudo curl -fsSLo /usr/share/keyrings/kubernetes-archive-keyring.gpg <https://packages.cloud.google.com/apt/doc/apt-key.gpg>
#第三步:添加 Kubernetes apt 仓库:
echo "deb [signed-by=/usr/share/keyrings/kubernetes-archive-keyring.gpg] <https://apt.kubernetes.io/> kubernetes-xenial main" | sudo tee /etc/apt/sources.list.d/kubernetes.list
#第四步:更新 apt 包索引,安装 kubelet、kubeadm 和 kubectl,并锁定其版本:
sudo apt-get update
sudo apt-get install -y kubectl kubelet kubeadm
sudo apt-mark hold kubelet kubeadm kubectl
#查看kubeadm版本,当前为1.24.2:
kubeadm config images list
3.6 安装cri-docker
当前k8s版本是1.24,已经取消dockershim,直接安装cri-docker替代dockershim即可。
#下载代码:
git clone <https://github.com/Mirantis/cri-dockerd.git>
#编译安装:
# Run these commands as root
###Install GO###
wget <https://storage.googleapis.com/golang/getgo/installer_linux>
chmod +x ./installer_linux
./installer_linux
source ~/.bash_profile
cd cri-dockerd
mkdir bin
go get && go build -o bin/cri-dockerd
mkdir -p /usr/local/bin
install -o root -g root -m 0755 bin/cri-dockerd /usr/local/bin/cri-dockerd
cp -a packaging/systemd/* /etc/systemd/system
sed -i -e 's,/usr/bin/cri-dockerd,/usr/local/bin/cri-dockerd,' /etc/systemd/system/cri-docker.service
systemctl daemon-reload
systemctl enable cri-docker.service
systemctl enable --now cri-docker.socket
查看cri-docker是否启动:systemctl status cri-docker
3.7 配置iptables
由于k8s集群开的端口比较随机,关闭drop以免无法访问:
vim /etc/iptables.netease
# 注释掉DROP这一行
#-A INPUT -j DROP
重启iptables:
iptables-restore < /etc/iptables.netease
4. 部署k8s集群
4.1 部署master
在gdc-k8smaster01-taylor机器上,执行:
kubeadm init --apiserver-advertise-address=主机ip --kubernetes-version v1.24.2 --service-cidr=10.96.0.0/12 --pod-network-cidr=10.244.0.0/16 --cri-socket unix:///var/run/cri-dockerd.sock
如果成功,会显示:
按照指示,先创建kubelet的配置:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
备注:如果要重置k8s集群,就在k8s所有节点中使用reset命令:
kubeadm reset --cri-socket unix:///var/run/cri-dockerd.sock
ipvsadm --clear
rm $HOME/.kube/config
rm -r /etc/cni/net.d
重置后,重新执行kubeadm init xxx操作
。
4.2 部署node
在每台node上,执行master生成的join命令,例如:
kubeadm join 主机ip:6443 --token rromzk.x0bg9jekdr6r2a2i --discovery-token-ca-cert-hash sha256:a2a6eda7ffcd149bb6866fc9ece0b5cbb0e7738c4797db54af312891ea50b785 --cri-socket unix:///var/run/cri-dockerd.sock
注意:一定要在iptables中开放6443端口,不然无法部署Node节点。
配置node节点的kubectl环境:
将master上的/root/.kube/config文件拷贝到node节点上,然后执行sudo chown $(id -u):$(id -g) $HOME/.kube/config
。
k8s 重新生成token加入集群:
当master创建了1天后,k8s有新node要加入,则需要在master上重新生成token和hash值:
# 创建token,并生成完整的join node的命令
kubeadm token create --print-join-command
# 重新加入集群
kubeadm join <master ip>:6443 --token <token> --discovery-token-ca-cert-hash <hash> --cri-socket unix:///var/run/cri-dockerd.sock
4.3 master节点配置网络
执行:
wget <https://projectcalico.docs.tigera.io/manifests/calico.yaml> > application/calico.yaml
配置网络:
kubectl apply -f application/calico.yaml
查看状态calico状态:
kubectl -n kube-system get pod |grep calico
如果要重装网络插件:
# 基于原先的flannel配置,删除对应资源
kubectl delete -f kube-flannel.yml
# 删除相关配置
ifconfig cni0 down
rm -rf /var/lib/cni/
ifconfig flannel.1 down
ip link delete flannel.1
rm -f /etc/cni/net.d/*
systemctl restart kubelet
4.4 验收K8S集群
如下创建了1个master,4个node的集群:
4.5 测试K8S集群
创建目录及文件:
mkdir application/nginx
vim application/nginx/deployment.yaml
deployment.yaml控制器配置如下:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
replicas: 3 # tells deployment to run 2 pods matching the template
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
创建service资源:
vim application/nginx/service.yml
service.yml文件内容如下:
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
nodePort: 30080
type: NodePort
请求master,创建deplyment控制器和service:
kubectl apply -f application/nginx/deployment.yaml
kubectl apply -f application/nginx/service.yml
查看pod所在机器:kubectl get pods -l app=nginx -o wide
此时pod创建在gdc-k8snode03-taylor.i.nease.net节点上,访问nginx服务:
删除一个pod:delete pod nginx-deployment-6595874d85-plv7q
,会立刻在当前节点重建一个pod:
删除/下线一个节点:pod会自动在另一个节点上创建。如下所示:先通过cordon下线节点gdc-k8snode03-taylor.i.nease.net
,并通过drain驱逐gdc-k8snode03-taylor.i.nease.net
节点上的pod,k8s会自动在新节点gdc-k8snode04-taylor.i.nease.net
上创建pod:
4.6 K8S集群常用操作
常用命令:
- 根据YAML配置创建资源:
kubectl apply -f xxx.yaml
- 查询deployment相关信息:
kubectl describe deployment nginx-deployment
- 列出指定标签的pod列表:
kubectl get pods -l app=nginx -o wide
- 查看某一个pod信息:
kubectl describe pod <pod-name>
- 查看所有deployment:
kubectl get deployment
- 删除deployment:
kubectl delete deployment nginx-deployment
- <span style="color:#FF0000">进入容器</span>:
kubectl exec -it <pod-name> -- /bin/bash
- <span style="color:#FF0000">查看pod日志</span>:
kubectl logs <pod-name> --tail 100
- 查看所有service:
kubectl get service
- 删除指定service:
kubectl delete service nginx-service
- 查看节点详细信息:
kubectl describe node gdc-k8snode04-taylor.i.nease.net
- 查看cri-docker服务日志:
journalctl -u cri-docker.service
- 删除节点:
kubectl delete node gdc-k8snode04-taylor.i.nease.net
。注意,删除后,需要reset节点,再join。 - cordon下线节点(<span style="color:#FF0000">推荐</span>):
- 下线节点:
kubectl cordon node1
- 驱逐pod:
kubectl drain node1
- 上线节点:
kubectl uncordon node1
- 下线节点: