Linux & Docker & K8S

Linux基本操作

ssh登陆

# install ssh
apt-get install ssh
# 生成秘钥对
ssh-keygen -t rsa -C "any comment can be here"
# 将自己生成的公钥床上到目标机器上,这样可以实现免密登陆
ssh-copy-id -f -i /root/.ssh/xxx.pub root@jumpserver.com
# 远程登录
ssh -p 22 root@jumpserver.com
# 在远程机器执行命令 usage: ssh destination [command]
ssh -p 22 root@jumpserver.com "echo hi"

Linux文件夹说明

[liao@pc1 /]$ tree -L 1
.
|-- bin -> usr/bin
|-- boot
|-- dev
|-- etc
|-- home
|-- lib -> usr/lib
|-- lib64 -> usr/lib64
|-- media
|-- mnt
|-- opt
|-- proc
|-- root
|-- run
|-- sbin -> usr/sbin
|-- srv
|-- sys
|-- tmp
|-- usr
|-- var
  • /bin:bin是Binary的缩写, 这个目录存放着最经常使用的命令。

  • /etc:这个目录用来存放所有的系统管理所需要的配置文件和子目录。

  • /home:用户的主目录,在Linux中,每个用户都有一个自己的目录,一般该目录名是以用户的账号命名的。

  • /lib:这个目录里存放着系统最基本的动态连接共享库,其作用类似于Windows里的DLL文件。几乎所有的应用程序都需要用到这些共享库。

  • /opt:这是给主机额外安装软件所摆放的目录。比如你安装一个ORACLE数据库则就可以放到这个目录下。默认是空的。

  • /root:该目录为系统管理员,也称作超级权限者的用户主目录。

  • /sbin:s就是Super User的意思,这里存放的是系统管理员使用的系统管理程序。

  • /tmp:这个目录是用来存放一些临时文件的。

  • /usr:这是一个非常重要的目录,用户的很多应用程序和文件都放在这个目录下,类似于windows下的program files目录。

  • /usr/bin:系统用户使用的应用程序。

  • /usr/sbin:超级用户使用的比较高级的管理程序和系统守护程序。

  • /usr/src:内核源代码默认的放置目录。

  • /usr/local:本地系统管理员用来自由添加程序的目录

  • /var:这个目录中存放着在不断扩充着的东西,我们习惯将那些经常被修改的目录放在这个目录下。包括各种日志文件。

在 Linux 系统中,有几个目录是比较重要的,平时需要注意不要误删除或者随意更改内部文件。

  • /etc: 上边也提到了,这个是系统中的配置文件,如果你更改了该目录下的某个文件可能会导致系统不能启动。

  • /bin, /sbin, /usr/bin, /usr/sbin: 这是系统预设的执行文件的放置目录,比如 ls 就是在/bin/ls 目录下的。值得提出的是,/bin, /usr/bin 是给系统用户使用的指令(除root外的通用户),而/sbin, /usr/sbin 则是给root使用的指令。

  • /var: 这是一个非常重要的目录,系统上跑了很多程序,那么每个程序都会有相应的日志产生,而这些日志就被记录到这个目录下,具体在/var/log 目录下,另外mail的预设放置也是在这里。

Linux文件夹权限

> ll busybox.yml
-rw-r--r--  1 yxliao  staff   963B  6  8 17:49 busybox.yml

drwxr-xr-x -> 1.3.3.3 -> (file/dir)(owner)(group)(other)

# 给文件组加权限
chmod g+w filename
# 给文件组减权限
chmod g-wx filename
# 给其他用户加权限
chmod o+w filename
# 给其他用户减权限
chmod o-rwx foldername

命令行基本操作

自动补全 : 在linux的下,输入命令后连续按两次 tab 按键,系统便会出现命令提示或自动补全。 这个功能其实就是一段 shell 脚本放在自动补全的脚本文件夹下。 一般的开源项目,都在 github 会提供对应的自动补全脚本,可自行安装。 更多内容请自行 Google “bash auto completion”!安装完成自动提示后,执行exec bash即可使用

小技巧

一般的 linux 下,命令后加 -h--help 可以显示简单的命令使用提示,如:brew -h

# 查系统信息
cat /etc/os-release
# 查网卡ip
ifconfig
# 查外网ip,需要能访问外网
curl ifconfig.me
# 切换目录
cd ~
# 显示目录下全部文件
ls -ashl
# 已树状结构显示文件夹
tree -L 1

文件移动、复制、删除

# 移动文件或文件夹。这里要注意 目录后是否有 '/',
# 一般带'/',会将文件夹整个移动或复制到目标文件夹下,而不是源文件夹下的文件
mv nginx.log newDir/
mv dir1/*  dir2/
# 复制文件或文件夹。 这里要注意 目录后是否有 '/'
cp -f nginx.log copyDir/
cp -fr dir1/ dir2/
# 删除文件/文件夹, 同时删除 log.log 文件和 logs 文件夹
rm -rf log.log logs/
# 查看文件属性
file nginx.log

文本文件操作

# 新建文件夹
mkdir /var/log/nginx/
# 新建文本文件
touch /var/log/nginx/nginx.log
# 使用编辑工具编辑文本
vim /var/log/nginx/nginx.log
# 读取文本全部文件 -n 显示行号
cat -n /var/log/nginx/nginx.log
# 读取全部文件内容,并过滤包含keyword的行
cat -n /var/log/nginx/nginx.log | grep "keyword"
# 读开头10行
head -n 10 /var/log/nginx/nginx.log
# 读最后10行
tail -n +10 /var/log/nginx/nginx.log

文本编辑器 vim

vim 是我们在 linux 中比较常用的文本编辑工具。详细的使用请大家自行 google 。这里仅展示常见命令。

  • 命令模式(默认),按 ESC 键进入

  • 输入模式, 在命令模式时,按 i 键进入

  • 末行模式, 在命令模式时,按 : 键进入

# 末行模式下
:q # 退出
:wq # 保存并退出
:q! # 不保存并退出
:w # 保存
:w! # 强行保存

# 命令模式下
gg # 定位到第1行
[n]gg # 定位到指定行
G # 定位到最后1行
gg = G # 格式化
i # 在光标处插入
o # 在下一行插入
[n]dd # 删除当前行
[n]yy # 复制当前行
p # 插入复制内容
u # 撤销上次编辑
ggyG # 复制全部

安装软件(包管理器)

各版本的 linux 系统安装后都有带有自己的包管理器

  • 原生的 linux: aptapt-get

  • macos: brew

  • centos: yum

  • ubuntu: dpkgapt-get

这些包管理器基本的的使用方法都差不多。主要的基本命令如:

  • list:列出已安装组件包

  • search:在包源搜索可用组件包

  • install:安装包

  • uninstall:卸载包

  • upgrade:升级已经安装组件

# 以安装 Nginx 为例
apt update && apt upgrade
apt search nginx
apt install nginx

Linux 服务管理

systemctl 命令

systemctl是linux系统初始化管理系统systemd的管理命令。 systemd 也是一个命令,还有很多附带命令,可以查看系统启动路径,各路径启动时间。

service 命令

service 其实就是一段普通脚本。它会初始化一些环境变量,然后到rc.d/init.d/找对应名称的脚本进行执行。(不同版本的操作系统位置会有所差异。) 一般,这些脚本文件会指定 start、stop、reload 等命令需要执行的操作。

注意

目前主流的 linux 发行版在淘汰 service 命令。更多的推荐使用 systemctl 命令。

# 列出当前系统服务状态
systemctl list-units
# 看状态
systemctl status nginx.service

nginx.service - A high performance web server and a reverse proxy server
   Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled) 
   Active: active (running) since Sun 2020-06-28 21:20:22 CST; 2 weeks 2 days ago

# 启动nginx服务
systemctl start nginx.service

# 添加自己的任务 此路径为查看任务状态中的 Loaded 部分 (不同发行版本的路径可能不同)
cd /lib/systemd/system/
cp nginx.service my.service
vim my.service
# 查服务日志
journalctl -u kubelet -r --since "2022-04-19 16:30:00" --until "2022-04-19 17:00:00" -o json-pretty > kubelet.log

查看系统资源(内存、cpu)使用

top
htop
free -h

查磁盘文件占用

du -h --max-depth=1 按目录向下查询文件大小

找文件、文件中内容

  • find 通过文件名称,匹配符找文件或文件夹

  • grep 查文件中内容

大文件分割、合并

# 将大文件拆分
split -b 100M file.tar.gz "split-"
# 合并大文件
cat split-* > file.tar.gz

找进程 & 杀进程

  1. 使用top命令,查看资源使用情况。其中能看到pid

  2. 使用ps -ef | grep java查看 java 启动文件名称。

  3. 使用pwdx <PID>输出进程启动目录。

  4. pgrep, pkill look up or signal processes based on name and other attributes

  5. ps -ef | grep nginx | wc -l 查进程数

  6. pidof nginx 获取进程号

  7. kill -9 <PID> 杀进程

查线程

  1. ps -T -p <pid> SID栏表示线程ID,而CMD栏则显示了线程名称

  2. top -H -p <pid>

查端口

# 查看所有80端口使用情况
netstat -ntulp | grep 80
# 查看一台服务器上面哪些服务及端口
netstat -lanp
# 查看一个服务有几个端口。比如要查看 mysqld
ps -ef | grep mysqld
# 查看某一端口的连接客户端IP 比如3306端口
netstat -anp | grep 3306
# 查看并发请求数及其TCP连接状态
netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'

测网络

# ping
ping <ip>
# 查看当前机器对域名的解析情况
nslookup www.demo.com
# 测试端口是否通联
netcat -zv 10.0.0.53 1443

远程文件上传下载

scp (secure copy)

将本地文件拷贝到远程服务器: scp <-P port> localFile user@host:~/remoteFile

将远程服务器中的文件拷贝到本地的用法: scp <-P port> user@host:~/remoteFile localFile

压缩上传: scp <-P port> -C localFile user@host:~/remoteFile

sftp(Secure File Transfer Protocol)

sftp username@serverip

输入密码链接后,会有两个映射文件夹。命令中l开头的是本地。不带l的可查看远程文件夹信息。

使用help查看帮助

# 全命令行操作
sftp username@serverip << EOF
cd [dist dir]
put [file name]
exit
EOF

Docker基本操作

查docker信息

# 查安装版本
docker --version
# 查看docker具体信息,保存根目录、使用的存储、包源地址等
docker info

镜像,仓库

镜像就是 docker 运行的对象,里面需要将我们的代码也打包进去,并指定好启动入口点。

# 列出下载到本机的进行
docker image ls
# 从镜像仓库搜索镜像
docker search centos
# 从仓库拉镜像到本机
docker pull centos
# 推镜像
docker push my-centos
# 从本机删除一个镜像
docker rmi my-centos

容器

容器是镜像运行的具体实例。

# 查看全部正在运行的容器
docker ps -a
# 使用指定镜像创建容器
docker run -d \ 
     -v /localhost/webapp:/usr/share/nginx/html \
     -e service_name=xiaoxin \
     --name name --network host \
     my-centos:dev
# 启动容器
docker start b750bbbcfd88
# 停止容器
docker stop b750bbbcfd88
# 进入容器并执行 /bin/bash
docker exec -it b750bbbcfd88 /bin/bash
# 删除容器
docker rm -f b750bbbcfd88
# 查看容器的标准输出
docker logs -f b750bbbcfd88
# 查看容器状态
docker inspect
# 查看容器的指标
docker stats
# 往容器复制文件
docker cp foo.txt b750bbbcfd88:/foo.txt
# 从容器复制文件
docker cp b750bbbcfd88:/foo.txt foo.txt

Dockerfile 制作自己的镜像

Dockerfile 定义镜像文件。包括打包本地文件到镜像内,设置镜像内文件权限,执行命令,启动入口等。。
当定义好 Dockerfile ,使用 docker build 进行编译来获得镜像文件。当构造完成使用docker images查看本地镜像列表。

# 创建 dockerfile
touch Dockerfile
# 编译 dockerfile
docker build -t my-centos:dev .
# 编译完成,查看本地镜像
docker images
# 使用刚编译的进行创建容器
docker run --publish 8000:8080 --detach --name demo my-centos:dev
# 删除容器
docker rm --force demo

dockerfile 基本内容:

# FROM 命令指定容器的基础镜像.
FROM node:current-slim
# 设置工作目录.
WORKDIR /usr/src/app
# 将本机的文件到容器的工作目录
COPY . .
# 在容器中执行 shell 命令.
RUN npm install
# 设置环境变量
ENV service_name=xiaoxin
# 指定容器暴露的端口
EXPOSE 8080
# 容器启动后执行的命令.
CMD [ "npm", "start" ]
# for debugger
ENTRYPOINT ["tail", "-f", "/dev/null"]

注意

注意 : docker 镜像在生成时,一行命令就叠加一层,为了控制镜像大小,注意合并命令

  • 你至少得定义一个(ENTRYPOINT 或者CMD),以保证运行。否则容器跑不起来

  • 你的容器运行时只定义它们其中一个的话,CMD和ENTRYPOINT的效果是一样的

  • ENTRYPOINT 和 CMD 都可以被 docker run -- entrypoint 参数覆盖

镜像备份、还原

小技巧

save/load 方式
docker save默认是往标准输出输出内容。也可以使用gzip将文件压缩。

# 用新名称保存修改后的容器
docker commit -p container [repository[:tag]]
# 导出镜像文件
docker save -o image.tar image 
# 导入
docker load --input image.tar

小技巧

export/import 方式

# 查看当前正在运行的容器
docker ps
# 导出某个容器
docker export f299f501774c > busybox.tar
# 导入
docker import - busybox < busybox.tar

基本运维 & 注意事项

  • 日志控制大小,长时间会占满全部磁盘空间

  • 定期清理镜像,长时间会占满全部磁盘空间

# 批量删除停止的容器:
docker ps -a | grep Exit | cut -d ' ' -f 1 | xargs docker rm
# 用于清理磁盘,删除关闭的容器、无用的数据卷和网络,以及dangling镜像
docker system prune
# 服务启动失败,查看失败原因
dockerd
# 增加 docker 守护程序关于日志的配置
vim /etc/docker/daemon.json
systemctl daemon-reload && systemctl restart docker

小技巧

daemon.json

{
  "registry-mirrors": ["https://registry.cn-hangzhou.aliyuncs.com"],
  "exec-opts": ["native.cgroupdriver=systemd"],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "500m",
    "max-file": "1"
  }
}

Docker Desktop

Docker 官方提供的快速安装和管理 docker 的可视化工具。官方地址

docker-desktop

K8S基本知识

容器化的发展

container_evolution

容器运行时 & K8S架构

容器运行时是一个负责运行容器的软件,以前是 docker ,现在比较流行的有: containerd 、 CRI-O 、 Docker Engine 等 。 container_evolution

K8S 为了调度不同的运行时,K8S 定义了一套运行时的标准接口,称做: CRI (Container Runtime Interface)。 kubelet 组件通过标准 CRI 来控制底层的容器的生命周期。 container_evolution

docker-containerd-runc

当有了容器运行时后,一套为了管理容器调度、容器生命周期的软件便应运而生,这就是我们说的 K8S。

一般的,我们把K8S集群中的 linux 机器分为两类:

  • 控制平面(Control Plane) 存储了K8S中所有相关的配置和调度计划。并有程序负责调度容器和监控容器状态。

  • 工作节点(Worker Node) 最终运行容器的机器。 components-of-kubernetes

k8s 通过一个叫 kube-apiserver 的软件来提供的 http api 接收用户创建和修改容器的请求。 并将用户的期望结果存储在 etcd 数据库软件中。 随后, kube-scheduler 软件会不停的监听着 etcd 数据库中的数据变化。 当发现变化后,通过相关计算决定容器需要被调度到哪里。并将调度结果回写到 etcd 数据库中。

各个节点上的 kubelet 软件通过 http api 查询到有关调度信息后,开始控制本节点上的容器运行时进行容器的创建或者销毁。

Pod 的生命周期

  1. 通过 apiserver 创建一个 pod 。数据将先被保存在 etcd 中。

  2. k8s其他组件通过 watch 机制感知数据变化,并发现新的 pod 没有绑定到任何的 node 上。

  3. 随后, kube-scheduler 开始为此 pod 分配节点,并绑定 podnode 信息,回写到 etcd 中。

  4. 数据回写完成后,kubelet 通过 watch 机制发现新 pod 。并开始执行启动 container 等命令。

  5. 容器启动完成后,kubelet 将启动完成等信息回写到 etcd

pod_lifecycle

集群网络

我们在初始化整个集群节点时需要指定,集群中的虚拟IP地址段。

networking:
  dnsDomain: cluster.local
  serviceSubnet: 10.96.0.0/16 # k8s service 的地址段
  podSubnet: 10.100.0.1/16 # k8s 的 pod 地址段

在集群中的每个节点上,都会运行着一个叫 kube-proxy 的网络代理软件。 它会监听在 etcd 中的网络相关的配置变化,并将网络转发及路由配置到宿主机器上的 iptables 和 route 中。

两点核心点:

  1. kube-proxy 主要解决从 ClusterIP 重定向到 PodIP

  2. cni 插件主要解决 PodIP 到 PodIP 的通讯

calico cni 的 Pod 见相互通讯机制

calico-network

在安装完 cni 插件后,节点机器中就多出了一些虚拟网卡。

 1# 网卡列表
 2[root@k8s-mas1 /] ip a
 31: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
 4    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
 5    inet 127.0.0.1/8 scope host lo
 62: ens160: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
 7    link/ether 00:50:56:90:bb:fd brd ff:ff:ff:ff:ff:ff
 8    inet 10.0.41.11/25 brd 10.0.41.127 scope global noprefixroute ens160
 93: ens192: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
10    link/ether 00:50:56:90:2f:3a brd ff:ff:ff:ff:ff:ff
11    inet 10.0.40.11/25 brd 10.0.40.127 scope global noprefixroute ens192
124: vxlan.calico: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default 
13    link/ether 66:aa:5f:b1:05:be brd ff:ff:ff:ff:ff:ff
14    inet 10.100.190.64/32 scope global vxlan.calico
1514: nerdctl0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default qlen 1000
16    link/ether 2a:d0:74:44:b6:25 brd ff:ff:ff:ff:ff:ff
17    inet 10.4.0.1/24 brd 10.4.0.255 scope global nerdctl0
1831: calicf240f7ad2e@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default 
19    link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff link-netnsid 1
2032: calidf57cc768d8@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default 
21    link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff link-netnsid 0

当集群中多出现一个 Pod IP 时就会多一行 route 。 PodIP -> calicXXX

 1# 路由列表
 2[root@k8s-mas1 /] route -n
 3Kernel IP routing table
 4Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
 50.0.0.0         10.0.41.1       0.0.0.0         UG    100    0        0 ens160
 610.0.40.0       0.0.0.0         255.255.255.128 U     101    0        0 ens192
 710.0.41.0       0.0.0.0         255.255.255.128 U     100    0        0 ens160
 810.4.0.0        0.0.0.0         255.255.255.0   U     0      0        0 nerdctl0
 910.100.38.192   10.0.40.13      255.255.255.192 UG    0      0        0 ens192
1010.100.55.192   10.0.40.22      255.255.255.192 UG    0      0        0 ens192
1110.100.56.128   10.0.40.21      255.255.255.192 UG    0      0        0 ens192
1210.100.75.64    10.0.40.23      255.255.255.192 UG    0      0        0 ens192
1310.100.101.0    10.0.40.26      255.255.255.192 UG    0      0        0 ens192
1410.100.116.64   10.0.40.24      255.255.255.192 UG    0      0        0 ens192
1510.100.151.64   10.0.40.25      255.255.255.192 UG    0      0        0 ens192
1610.100.190.64   0.0.0.0         255.255.255.192 U     0      0        0 *
1710.100.190.80   0.0.0.0         255.255.255.255 UH    0      0        0 calicf240f7ad2e
1810.100.190.81   0.0.0.0         255.255.255.255 UH    0      0        0 calidf57cc768d8
1910.100.191.128  10.0.40.12      255.255.255.192 UG    0      0        0 ens192

iptables-diagram

K8S集群搭建

确认端口

k8s 集群需要使用端口:

节点

port

Control Plane

22,80,443,2376,2379,2380,6443,8090,8091,8472,8443,9099,9100,10250,10251,10252,10254,10255,30000-32767

Worker Node

22,80,443,8090,8472,9100,10250,10255,30000-32767

准备 Linux 服务器

每台机器都需要执行下面步骤

# 安装在 centos 7 3.10.0-957.5.1.el7.x86_64 系统下进行。

# 关闭系统交换区
swapoff -a
# 关闭防火墙 centos 7
systemctl stop firewalld
systemctl disable firewalld

# 配置桥接网络
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF

modprobe overlay
modprobe br_netfilter

cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables  = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward                 = 1
EOF

echo '1' > /proc/sys/net/bridge/bridge-nf-call-iptables
# 不重启情况下使配置生效
sysctl --system

# 将 SELinux 设置为 permissive 模式(相当于将其禁用)
setenforce 0
sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config

### 修改linux 包管理 yum 包源

# 备份系统原有源
mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup
# 下载阿里云 centos 包源 https://developer.aliyun.com/mirror/centos
wget -O /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo

# 添加阿里云 k8s 包源 https://developer.aliyun.com/mirror/kubernetes
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF

# 添加 docker-ce 包源
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

# 增加 yum 包源缓存
yum makecache

# 重启机器
reboot

安装容器运行时 containerd

每台机器都需要安装

# 安装必要依赖
yum install -y yum-utils
# 如果安装 containerd.io 提示依赖 container-selinux ,需要先去 centos 找到最新的安装包地址。执行安装。
yum install -y http://mirror.centos.org/centos/7/extras/x86_64/Packages/container-selinux-2.119.2-1.911c772.el7_8.noarch.rpm
#安装 conntrack , kubelet 依赖它
yum install conntrack-tools -y

# 查最新的版本号
yum list containerd.io --showduplicates | sort -r
# 执行安装
yum install containerd.io-1.6.12 -y
# 生成默认配置文件
containerd config default > /etc/containerd/config.toml

# 修改配置文件
vim /etc/containerd/config.toml

# 1.改 root 文件夹 (因为云服务开机器时指定的系统盘磁盘格式问题,不能直接支持) root = "/data/var/lib/containerd"  
# 2.重载沙箱(pause)镜像 [plugins."io.containerd.grpc.v1.cri"] sandbox_image = "registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.6"
# 3.配置 systemd cgroup 驱动, [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options] SystemdCgroup = true ,
# 4.镜像仓库地址
# [plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
#     endpoint = ["https://mirror.ccs.tencentyun.com"]
# [plugins."io.containerd.grpc.v1.cri".registry.mirrors."quay.io"]
#     endpoint = ["https://quay.tencentcloudcr.com"]

### 安装 containerd 的 cni 插件 

wget https://github.com/containernetworking/plugins/releases/download/v1.1.1/cni-plugins-linux-amd64-v1.1.1.tgz
mkdir -p /opt/cni/bin
tar Cxzvf /opt/cni/bin cni-plugins-linux-amd64-v1.1.1.tgz

# 启动 containerd 服务
systemctl daemon-reload
systemctl enable containerd
systemctl restart containerd

### 安装 crictl 交互工具 (非必要,但对 k8s 集群调试很有帮助) 

# https://github.com/kubernetes-sigs/cri-tools/blob/master/docs/crictl.md
VERSION="v1.25.0"
wget https://github.com/kubernetes-sigs/cri-tools/releases/download/$VERSION/crictl-$VERSION-linux-amd64.tar.gz
tar zxvf crictl-$VERSION-linux-amd64.tar.gz -C /usr/local/bin
rm -f crictl-$VERSION-linux-amd64.tar.gz

# 增加 crictl 配置文件
cat <<EOF > /etc/crictl.yaml 
runtime-endpoint: unix:///run/containerd/containerd.sock
image-endpoint: unix:///run/containerd/containerd.sock
timeout: 5
debug: false
pull-image-on-create: false
EOF

# 安装 crictl bash 自动完成
source <(crictl completion)

安装 kubeadm & kubelet

每台机器都需要安装

# 卸载旧版本
yum remove -y kubelet kubeadm kubectl
# 安装 kubelet kubeadm kubectl
yum install -y kubelet-1.26.0 kubeadm-1.26.0 kubectl-1.26.0 --disableexcludes=kubernetes
# 启动 kubelet
systemctl daemon-reload
systemctl enable --now kubelet
systemctl start kubelet

# 安装完成,查看版本
containerd --version
kubelet --version

初始化 master 节点

k8s-master-ha

# export 命令只在当前 shell 会话中有效,开启新的 shell 窗口后,如果要继续安装过程,请重新执行此处的 export 命令,设置当前主节点IP
export MASTER_IP=10.0.40.11
# 替换 APISERVER_NAME设置虚IP
export APISERVER_NAME=10.0.40.xx

# 生成 kubeadm 的配置文件
cat <<EOF > ./kubeadm-config.yaml
---
apiVersion: kubeadm.k8s.io/v1beta3
kind: InitConfiguration
localAPIEndpoint:
  advertiseAddress: "${MASTER_IP}"
  bindPort: 6443
nodeRegistration:
  criSocket: unix:///var/run/containerd/containerd.sock
  imagePullPolicy: IfNotPresent
  #name: k8s-mas1
  taints: null
---
apiServer:
  timeoutForControlPlane: 4m0s
apiVersion: kubeadm.k8s.io/v1beta3
certificatesDir: /etc/kubernetes/pki
clusterName: kubernetes
controlPlaneEndpoint: "${APISERVER_NAME}:6443"
controllerManager: {}
dns: {}
etcd:
  local:
    dataDir: /var/lib/etcd
imageRepository: registry.aliyuncs.com/google_containers
kind: ClusterConfiguration
kubernetesVersion: 1.26.0
networking:
  dnsDomain: cluster.local
  serviceSubnet: 10.96.0.0/16
  podSubnet: 10.100.0.1/16
scheduler: {}
---
kind: KubeletConfiguration
apiVersion: kubelet.config.k8s.io/v1beta1
cgroupDriver: systemd
logging:
  format: json
containerLogMaxSize: 100Mi
containerLogMaxFiles: 3
imageGCHighThresholdPercent: 80   # 触发镜像垃圾回收的磁盘使用率百分比
imageGCLowThresholdPercent: 60    # 镜像垃圾回收试图释放资源后达到的磁盘使用率百分
EOF

# 拉取 kubeadm 需要使用的镜像
kubeadm config images pull --config=kubeadm-config.yaml
# kubeadm 初始化 master 节点 (初始化节点后,会生成其他 master 节点 join 的命令,需要记住)
kubeadm init --config=kubeadm-config.yaml --upload-certs

# 初始化完成,配置 kubectl 访问 kube-api 的配置文件
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

# 安装 calico 网络插件 
# 官方文档:https://projectcalico.docs.tigera.io/getting-started/kubernetes/self-managed-onprem/onpremises
# 下载 yaml
wget https://raw.githubusercontent.com/projectcalico/calico/v3.24.5/manifests/tigera-operator.yaml
# 执行安装
kubectl create -f tigera-operator.yaml
#下载 custom-resources.yaml 文件
curl https://raw.githubusercontent.com/projectcalico/calico/v3.24.5/manifests/custom-resources.yaml -O
# 修改配置文件,添加指定网卡
# calicoNetwork:
#     nodeAddressAutodetectionV4:
#       canReach: 10.0.40.1
kubectl apply -f custom-resources.yaml

# 安装完成

其他 master 节点加入

# 生成工作节点安装需要的 join 参数
kubeadm token create --print-join-command
# 查看证书的 key
key kubeadm certs certificate-key

# 在 token create 输入的语句后追加 --control-plane --certificate-key <certificate-key>
# 使用以下的命令添加其他 master 节点。
kubeadm join 10.0.40.11:6443 --token njus80.ptct35sv7t1qq8c0 \
    --discovery-token-ca-cert-hash sha256:e1e367f7e3c60607df41ad6c316eee5c3eb3728a54d3e045bda4a1f1316bd47d \

worker 节点加入

# 生成工作节点安装需要的 join 参数
kubeadm token create --print-join-command
# 在工作节点执行
kubeadm join 10.0.40.11:6443 --token njus80.ptct35sv7t1qq8c0 \
        --discovery-token-ca-cert-hash sha256:e1e367f7e3c60607df41ad6c316eee5c3eb3728a54d3e045bda4a1f1316bd47d

dashboard 安装

集群管理 dashboard 我们使用了飞致云开源的 kubepi 产品来作为我们多集群的管理工具。官方文档

# 官方文档 https://kubeoperator.io/docs/kubepi/ 
# 创建 k8s 资源 yaml
cat <<EOF > kubepi-pvc.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: kubepi-pv
  labels:
    app: kubepi
spec:
  storageClassName: jcpt-sc
  capacity:
    storage: 4Gi
  volumeMode: Filesystem
  claimRef:
    name: kubepi-pv-claim
    namespace: kube-system
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  mountOptions:
    - vers=3,intr,soft
  nfs:
    server: myfs.demo.local
    path: /ifs/tylcnas2/new-k8s/kubepi
    readOnly: false

---

apiVersion: v1
kind: ServiceAccount
metadata:
  name: kubepi-user
  namespace: kube-system

---

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

---

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: kubepi-pv-claim
  namespace: kube-system
  labels:
    app.kubernetes.io/name: kubepi
spec:
  storageClassName: jcpt-sc
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 4Gi

---

apiVersion: v1
kind: Service
metadata:
  name: kubepi
  namespace: kube-system
spec:
  type: NodePort
  ports:
    - name: http
      port: 80
      targetPort: 80
      protocol: TCP
  selector:
    app.kubernetes.io/name: kubepi

---

apiVersion: apps/v1
kind: Deployment
metadata:
  name: kubepi
  namespace: kube-system
  labels:
    app.kubernetes.io/name: kubepi
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: kubepi
  template:
    metadata:
      labels:
        app.kubernetes.io/name: kubepi
    spec:
      containers:
        - name: kubepi
          image: kubeoperator/kubepi-server:v1.6.2
          imagePullPolicy: Always
          ports:
            - containerPort: 80
              protocol: TCP
          securityContext:
            privileged: true
          volumeMounts:
            - name: kubepi-persistent-storage
              mountPath: /var/lib/kubepi
      volumes:
        - name: kubepi-persistent-storage
          persistentVolumeClaim:
            claimName: kubepi-pv-claim
      nodeSelector:
        kubernetes.io/os: linux
        node-role.kubernetes.io/control-plane: ''
      tolerations:
        - key: node-role.kubernetes.io/control-plane
          operator: Equal
          effect: NoSchedule
        - key: node-role.kubernetes.io/master
          operator: Exists
          effect: NoSchedule

EOF
kubectl apply -f kubepi-pvc.yaml
# 初始账号 admin/kubepi

K8S基本操作

发布 dotnet core 项目

下面我们简单来介绍一下,我们如何从0开始把一个 dotnet 项目发布到 k8s 容器化管理平台上。

增加 dockerfile

FROM mcr.microsoft.com/dotnet/aspnet:5.0-buster-slim
WORKDIR /app
# 复制 dotnet 发布文件到容器的 /app 目录
COPY . /app
# 环境变量设置时区,会影响系统显示时间。
ENV TZ=Asia/Shanghai
# 修改操作系统的 openssl 配置,将最低协议变成 TLSv1,解决发起 https 请求时出现的 tls 版本错误问题。
RUN sed -i 's/MinProtocol = TLSv1.2/MinProtocol = TLSv1/' /etc/ssl/openssl.cnf \
    && sed -i 's/CipherString = DEFAULT@SECLEVEL=2/CipherString = DEFAULT@SECLEVEL=1/' /etc/ssl/openssl.cnf
#安装curl是为了yaml文件readinessProbe下使用
RUN sed -i "s/deb.debian.org/mirrors.aliyun.com/g" /etc/apt/sources.list
RUN  apt-get update  && apt-get install -y curl
# 容器启动后,要执行的程序
ENTRYPOINT ["dotnet", "Seazen.XiaoXin.dll"]
# 项目目录结构
├── README.md
└── src
    ├── Controllers
    ├── Properties
    ├── Program.cs
    ├── Startup.cs
    ├── appsettings.Development.json
    ├── appsettings.Production.json
    ├── appsettings.Staging.json
    ├── k8s-deploy.yaml
    ├── k8s-cm.yaml
    ├── k8s-service.yaml
    ├── Dockerfile
    ├── Jenkinsfile
    ├── Seazen.XiaoXin.csproj
    └── version.txt

jenkins增加持续集成

jenkins-xiaoxin

增加持久化配置(ConfigMap)

apiVersion: v1
kind: ConfigMap
metadata:
  name: xiaoxin-cm
  namespace: jcpt
data:
  # data 可以理解为一个文件夹,下面的每个 key 代表一个文件。
  filebeat.yml: |-
    logging.level: warning
    filebeat.inputs:
    - type: log
    enabled: true
    fields:
        server: xiaoxin
    fields_under_root: true
    multiline:
        pattern: '^[0-9]{4}-[0-9]{2}-[0-9]{2}'
        negate: true
        match: after  
    paths:
        - /app/logs/xiaoxin-*.log
    output.logstash:
    hosts: ["logstash.jcpt:5044"]
  appsettings.json: |-
    {
        "Logging": {
            "LogLevel": {
                "Default": "Information",
                "Microsoft": "Warning",
                "Microsoft.Hosting.Lifetime": "Information",
            }
        },
        "ConnectionStrings": {
            "SqlConnection": "server=10.0.0.11,1433;uid=sa;pwd=!1234%;database=MsgDB"
        }
    }

注意

一般,我们会把修改后不需要改动代码仅需要应用重启的配置变成 ConfigMap 处理。
如果是需要改动后实时生效的配置,请参考我们的配置中心相关功能。

增加工作负载(Deployment)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: xiaoxin
  namespace: jcpt
spec:
  replicas: 2
  selector:
    matchLabels:
    name: xiaoxin
  template:
    metadata:
    labels:
      name: xiaoxin
    spec:
    containers:
      # 镜像仓库的版本
      - name: xiaoxin
        image: demo.tencentcloudcr.com/qyfwzx/xiaoxin:dev.112
        imagePullPolicy: Always
        # 容器的就绪检查,当不返回 80 时, K8S 不会将流量调度到这个 Pod
        readinessProbe:
          exec:
            command:
              - curl
              - '--fail'
              - '-o'
              - /dev/null
              - 'http://localhost/api/readiness'
          periodSeconds: 10
          failureThreshold: 30
          successThreshold: 1
          timeoutSeconds: 1
        # 资源配额
        resources:
          limits:
            cpu: "200m"
            memory: 512M
          requests:
            cpu: "100m"
            memory: 512M
        # 增加应用需要的Linux系统环境变量
        env:
          - name: ASPNETCORE_ENVIRONMENT
            value: Production
          - name: ASPNETCORE_URLS
            value: "http://+:80"
        # 容器需要暴露的端口
        ports:
          - containerPort: 80
        # 容器挂载存储
        volumeMounts:
          # 将 configMp 挂载到容器的指定目录
          - name: appsettings
            mountPath: /app/appsettings.json
            subPath: appsettings.json
          # 将 nas 文件夹挂载到指定目录
          - name: ekpnfs
            mountPath: /mnt/ekp
    # 腾讯云镜像仓库的登录密钥,固定值,不需要更改
    imagePullSecrets:
      - name: tencent-ccr
    # 配置Pod的存储卷
    volumes:
      # configMap 类型存储
      - name: appsettings
        configMap:
          name: xiaoxin-cm
          items:
            - key: appsettings.json
              path: appsettings.json
      # nas 类型存储
      - name: ekpnfs
        nfs:
          server: 10.0.6.4
          path: /ifs/ekp14nas

注意

ConfigMap 挂载到一个目录时,相当于是在容器内重新创建一个文件夹, 并把 ConfigMap 定义中的内容以文件形式写入到容器中。所以,如果挂载容器的原始目录有文件,将会被全部删除。
同时,这种挂载方式在 K8S 中对 ConfigMap 的操作,都将更新到容器内。 但如果使用 ConfigMap 作为 subPath 的数据卷将不会收到 ConfigMap 更新。

增加 k8s 的网络(Services)

apiVersion: v1
kind: Service
metadata:
  name: xiaoxin
  namespace: jcpt
  labels:
    name: xiaoxin
spec:
  type: ClusterIP
  selector:
    name: xiaoxin
  ports:
    - name: tcp
      port: 80
      protocol: TCP
      targetPort: 80

注意

一般我们的 Service 的网络类型都应该是 ClusterIP 。应用的 API 应该对接服务发现和应用网关, 使请求流量被统一管理。
而如诸如 RabbitMQ、Mysql 等类型的组件,集群内应该统一使用 K8S 内部的 DNS 方式访问,不要使用 NodePort 将端口暴露出来。
rabbitmq-0.rabbitmq-headless.jcpt,rabbitmq-1.rabbitmq-headless.jcpt,rabbitmq-2.rabbitmq-headless.jcpt

为了网络安全,K8S 集群应该只暴露 80、443 端口,所有应用应该仅通过 Http 对外部提供服务。

发布应用

  1. 进入集群 kubepi-cluster-list

  2. 工作负载中 -> Deployment -> YAML -> 从文件读取 kubepi-deploy-upload

  3. 预览 yaml 内容,提交。 kubepi-deploy-add

小技巧

我们可以将 deploy configMap service 相关 yaml 内容合并到同一个 yaml 文件中。

就绪探针、存活探针

这两个探针会影响到 K8S 会不会把请求流量调度到 Pod 上。 一般,我们所有的应用都需要增加就绪探针,来告知 K8S 可以将 Service 网络流量分配到此 Pod。
另外,当我们对接了服务发现后,还需要在就绪探针中确保自己的已经在服务发现完成注册。

注意

要特别注意,服务还没启动完成,网络流量就负载到 Pod 的问题。

apiVersion: v1
kind: Pod
metadata:
  name: goproxy
  labels:
    app: goproxy
spec:
  containers:
    - name: goproxy
      image: k8s.gcr.io/goproxy:0.1
      ports:
        - containerPort: 80
      # 就绪探针
      readinessProbe:
        httpGet:
          path: /readiness
          port: 80
          httpHeaders:
            - name: Custom-Header
              value: Awesome
        initialDelaySeconds: 3
        periodSeconds: 3
        failureThreshold: 30
        successThreshold: 1
        timeoutSeconds: 1
      # 存活探针
      livenessProbe:
        httpGet:
          path: /healthz
          port: 80
          httpHeaders:
            - name: Custom-Header
              value: Awesome
        initialDelaySeconds: 3
        periodSeconds: 3
        failureThreshold: 30
        successThreshold: 1
        timeoutSeconds: 1
  • initialDelaySeconds :容器启动后要等待多少秒后才启动存活和就绪探测器, 默认是 0 秒,最小值是 0。

  • periodSeconds :执行探测的时间间隔(单位是秒)。默认是 10 秒。最小值是 1。

  • timeoutSeconds :探测的超时后等待多少秒。默认值是 1 秒。最小值是 1。

  • successThreshold :探测器在失败后,被视为成功的最小连续成功数。默认值是 1。 存活和启动探测的这个值必须是 1。最小值是 1。

  • failureThreshold :当探测失败时,Kubernetes 的重试次数。 对存活探测而言,放弃就意味着重新启动容器。 对就绪探测而言,放弃意味着 Pod 会被打上未就绪的标签。默认值是 3。最小值是 1

更新应用新版本

  1. 在 deployment 中找到需要更新版本的应用并进入查看详情。 kubepi-edit-image

  2. 在“调整镜像版本”中,更新应用最近的版本号。 kubepi-update-image

扩容、重启

  1. 扩容、缩容、重启应用 kubepi-scale

  2. 查看应用日志、进入容器、查看文件 kubepi-log kubepi-log-console

更新集群证书

官方指导文档

  1. 登录到 master 节点,检查证书日期、更新证书

     kubeadm alpha certs check-expiration
    
  2. 在证书未过期时,执行更新证书命令

    kubeadm alpha certs renew all
    
  3. 重起静态 Pod

     mv /etc/kubernetes/manifests/*.yaml /tmp/  
     # 约等30秒后,kube-apiserver 容器会停止,然后,再将清单文件移过来:  
     mv /tmp/*.yaml /etc/kubernetes/manifests/  
     # 检查docker是否重起
     docker ps | grep -E 'k8s_kube-apiserver|k8s_kube-controller-manager|k8s_kube-scheduler|k8s_etcd_etcd'