0%

arch

实例

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
46
47
➜  ~ ps aux | grep laravels | grep -v grep
root 29239 0.0 0.1 989924 42660 ? Sl 11:07 0:03 laravels: master process
root 29250 0.0 0.0 402248 23060 ? S 11:07 0:00 laravels: manager process
root 29259 0.0 0.5 635160 188684 ? S 11:07 0:06 laravels: worker process 0
root 29260 0.0 0.5 635148 192564 ? S 11:07 0:13 laravels: worker process 1
root 29261 0.0 0.5 633112 189896 ? S 11:07 0:10 laravels: worker process 2
root 29262 0.0 0.5 641240 194080 ? S 11:07 0:19 laravels: worker process 3
root 29263 0.0 0.5 620484 171616 ? S 11:07 0:11 laravels: worker process 4
root 29264 0.0 0.4 588592 146464 ? S 11:07 0:05 laravels: worker process 5
root 29265 0.0 0.4 512808 139264 ? S 11:07 0:03 laravels: worker process 6
root 29266 0.0 0.4 592672 145852 ? S 11:07 0:03 laravels: worker process 7
root 29267 0.0 0.3 551456 102340 ? S 11:07 0:04 laravels: worker process 8
root 29268 0.0 0.5 633128 186340 ? S 11:07 0:05 laravels: worker process 9
root 29269 0.0 0.2 537124 90532 ? S 11:07 0:04 laravels: worker process 10
root 29270 0.0 0.4 590644 147988 ? S 11:07 0:02 laravels: worker process 11
root 29271 0.0 0.2 526872 79992 ? S 11:07 0:05 laravels: worker process 12
root 29272 0.0 0.6 645412 197672 ? S 11:07 0:11 laravels: worker process 13
root 29273 0.0 1.3 904576 452132 ? S 11:07 0:15 laravels: worker process 14
root 29274 0.0 0.3 545384 101540 ? S 11:07 0:04 laravels: worker process 15

➜ ~ pstree -pa 29239
php,29239 // master 进程
├─php,29250 // manager 进程
│ ├─php,29259 // worker 0
│ ├─php,29260 // worker 1
│ ├─php,29261 // worker 2
│ ├─php,29262 // worker 3
│ ├─php,29263 // worker 4
│ ├─php,29264 // worker 5
│ ├─php,29265 // worker 6
│ ├─php,29266 // worker 7
│ ├─php,29267 // worker 8
│ ├─php,29268 // worker 9
│ ├─php,29269 // worker 10
│ ├─php,29270 // worker 11
│ ├─php,29271 // worker 12
│ ├─php,29272 // worker 13
│ ├─php,29273 // worker 14
│ └─php,29274 // worker 15
├─{php},29251 // {php} 开头的都是 Master 里创建的线程,这里有 8 个
├─{php},29252
├─{php},29253
├─{php},29254
├─{php},29255
├─{php},29256
├─{php},29257
└─{php},29258

我们可以看到,Master 进程是多线程的,但是 Worker 是单线程的。

Master 进程

是一个多线程的程序。其中有一组很重要的线程,称之为 Reactor 线程。它就是真正处理 TCP 连接,收发数据的线程。

多线程使用 pthread 线程库。

Manager 进程

Manager 进程由 Master 进程 fork。

管理 Worker/Task Worker 进程,Worker/Task Worker 进程都是由 Manager 进程 fork 并管理的。

  • 子进程结束运行时,manager 进程负责回收此子进程,避免称为僵尸进程。并创建新的子进程。
  • 服务器关闭时(swoole server),manager 进程将发信号给所有子进程,通知子进程关闭服务。
  • 服务器 reload 时(swoole server),manager 进程会逐个关闭/重启子进程。

为什么不是 Master 进程呢,主要原因是 Master 进程是多线程的,不能安全的执行 fork 操作。

Reactor 线程

主线程(Master 进程)在 accept 新的连接后,会将这个连接分配给一个固定的 Reactor 线程,并由这个线程负责监听此 socket。在 socket 可读时读取数据,并进行协议解析,将请求投递到 Worker 进程。

  • 负责维护 TCP 连接、处理网络 IO、处理协议、收发数据。
  • 完全是异步非阻塞的模式
  • 除 Start/Shutdown 事件回调外,不执行任何 PHP 代码
  • 将 TCP 客户端发来的数据缓冲、拼接、拆分成完整的一个请求数据包
  • Reactor 以多线程的方式运行

分配的计算方式是 fd % serv->reactor_num

Worker 进程

类似于 php-fpm 进程。

  • 接受由 Reactor 线程投递的请求数据包,并执行 PHP 回调函数处理数据。
  • 生成响应数据并发给 Reactor 线程,由 Reactor 线程发送给 TCP 客户端
  • 可以是异步模式,也可以是同步模式
  • Worker 以多进程的方式进行

关系

可以理解为 Reactor 就是 nginx,Worker 就是 php-fpm。Reactor 线程异步并行地处理网络请求,然后再转发给 Worker 进程中去处理(在回调函数中处理)。Reactor 和 Worker 间通过 UnixSocket 进行通信。

一个更通俗的比喻,假设 Server 就是一个工厂,那 Reactor 就是销售,接受客户订单。而 Worker 就是工人,当销售接到订单后,Worker 去工作生产出客户要的东西。而 Task Worker 可以理解为行政人员,可以帮助 Worker 干些杂事,让 Worker 专心工作。

Reactor

它要求主线程(I/O 处理单元)只负责监听文件描述符上是否有事件发生,有的话就立即将该事件通知工作线程/进程(逻辑单元)。除此之外,主线程不做任何其他工作。读写数据,接受新的连接,以及处理客户请求均在工作线程中完成。

swoole 事件图

events

一个请求经历的步骤:

1、服务器主线程等待客户端连接 2、Reactor 线程处理连接 socket,读取 socket 上的请求数据(receive),将请求封装好后投递给 Worker 进程 3、Worker 进程就是逻辑单元,处理业务数据 4、Worker 进程结果返回给 Reactor 线程 5、Reactor 线程将结果写回 socket(Send)

所以,跟客户端交互的其实是 Reactor 线程,Reactor 线程作为沟通客户端跟后端的中介。

资源管理介绍

在 kubernetes 中,所有的内容都抽象为资源,用户需要通过操作资源来管理 kubernetes。

kubernetes 的本质上就是一个集群系统,用户可以在集群中部署各种服务,所谓的部署服务,其实就是在 kubernetes 集群 中运行一个个的容器,并将指定的程序跑在容器中。

kubernetes 的最小管理单元是 Pod 而不是容器,所以只能将容器放在 Pod 中,而 kubernetes 一般也不会直接管理 Pod, 而是通过 Pod 控制器 来管理 Pod 的。

Pod 可以提供服务之后,就要考虑如何访问 Pod 中的服务,kubernetes 提供了 Service 资源实现这个功能。

当然,如果 Pod 中程序的数据需要持久化,kubernetes 还提供了各种存储系统。

k8s-resource

外部访问的入口是 service。

学习 kubernetes 的核心,就是学习如何对集群上的 PodPod 控制器Service存储等各种资源进行操作。

yaml 注意事项

  1. 书写 yaml 切记 : 后面要加一个空格
  2. 如果需要将多段 yaml 配置放在一个文件中,中间要使用 --- 分隔

资源管理方式

  • 命令式对象管理:直接使用命令去操作 kubernetes 资源
1
kubectl run nginx-pod --image=nginx:1.17.1 --port=80
  • 命令式对象配置:通过命令配置和配置文件去操作 kubernetes 资源
1
kubectl create/patch -f nginx-pod.yaml
  • 声明式对象配置:通过 apply 命令和配置文件去操作 kubernetes 资源
1
2
# 适用于 创建或更新 资源
kubectl apply -f nginx-pod.yaml
类型 操作对象 适用环境 优点 缺点
命令式对象管理 对象 测试 简单 只能操作活动对象,无法审计、跟踪
命令式对象配置 文件 开发 可以审计、跟踪 项目大时,配置文件多,操作麻烦
声明式对象配置 目录 开发 支持目录操作 意外情况下难以调试

命令式对象管理

kubectl 命令

kubectl 是 kubernetes 集群的命令行工具,通过它能够对集群本身进行管理,并能够在集群上进行容器化应用的安装部署。kubectl 命令的语法如下:

1
kubectl [command] [type] [name] [flags]

command:指定要对资源执行的操作,例如 creategetdelete

type:指定资源类型,比如 deploymentpodservice

name:指定资源的名称,名称大小写敏感

flags:指定额外的可选参数

1
2
3
4
5
6
7
8
# 查看所有 pod
kubectl get pod

# 查看某个 pod
kubectl get pod pod_name

# 查看某个 pod,以 yaml 格式展示结果
kubectl get pod pod_name -o yaml

资源类型

kubernetes 中所有的内容都抽象为资源,可以通过下面的命令进行查看:

1
kubectl api-resources

经常使用的资源有下面这些:

  • 集群级别资源
    • nodes(no):集群组成部分
    • namespaces(ns):隔离 Pod
  • Pod 资源
    • pods(po):装载容器
    • replicationcontrollers(rc):控制 pod 资源
    • replicasets(rs):控制 pod 资源
    • deployments(deploy)控制 pod 资源
    • daemonsets(ds):控制 pod 资源
    • jobs:控制 pod 资源
    • cronjobs(cj):控制 pod 资源
    • horizontalpodautoscalers(hpa):控制 pod 资源
    • statefulsets(sts):控制 pod 资源
  • 服务发现资源
    • services(svc):统一 pod 对外接口
    • ingress(ing):统一 pod 对外接口
  • 存储资源
    • volumeattachments:存储
    • persistentvolumes(pv):存储
    • persistentvolumeclaims(pvc):存储
  • 配置资源
    • configmaps(cm):配置
    • secrets:配置

操作

kubernetes 允许对资源进行多种操作,可以通过 --help 查看详细的操作命令

1
kubectl --help

经常使用的操作有下面这些:

  • 基本命令
    • create:创建一个资源
    • edit:编辑一个资源
    • get:获取一个资源
    • patch:更新一个资源
    • delete:删除一个资源
    • explain:展示资源文档
  • 运行和调试
    • run:在集群中运行一个指定的镜像
    • expose:暴露资源为 Service
    • describe:现实资源内部信息
    • logs:输出容器在 pod 中的日志
    • attach:进入运行中的容器
    • exec:执行容器中的一个命令
    • cp:在 Pod 外复制文件
    • rollout:管理资源的发布
    • scale:扩(缩)容 Pod 的数量
    • autoscale:自动调整 Pod 的数量
  • 高级命令
    • apply:通过文件对资源进行配置
    • label:更新资源上的标签
  • 其他命令
    • cluster-info:显示集群信息
    • version:显示当前 Server 和 Client 的版本

下面以一个 namespace 的创建和删除简单演示下命令的使用:

1
2
3
4
5
6
# 创建一个 namespace
[root@master ~]# kubectl create namespace dev
namespace/dev created

# 获取 namespace
[root@master ~]# kubectl get ns

问题

生产环境使用了 horizon 来处理队列数据,但是在 supervisor 自动重启的过程中,发现有时候会有一些消费者进程在部署的时候无法正常被 kill 掉,导致一些旧的代码消费队列数据,从而引起报错。

原因是可能在重启的时候,那个消费者恰好在处理长时间的任务,导致无法被 kill 掉。但无法肯定是这个原因。

解决方法

每分钟 kill 掉那些不是当前 horizon 子孙进程的消费者(S 状态才会 kill 掉,保证正常业务不受影响)。

解决思路

  1. 通过 shell_exec 来执行 pspstree 等命令查看当前的进程信息,从而找出那些异常的消费者进程 id。
  2. 判断这些异常进程是否没有在处理任务。
  3. kill 掉 S 状态的异常消费者进程。

注意:shell_exec 里面使用 ps aux 可能无法获取正常在终端运行的结果,需要使用 ps -efww 才可以获取完整的输出。

实现代码

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;

class ClearInvalidQueueConsumer extends Command
{
protected $signature = 'clear-invalid-queue-consumer';

protected $description = '清除无效的队列消费者进程';

public function handle()
{
$noParentPids = $this->noParentPids();
$sleepingProcessPids = $this->getSleepingProcessPids($noParentPids);

if ($sleepingProcessPids) {
Log::info('kill 掉无效的队列进程:', $sleepingProcessPids);
$this->killInvalidProcess($sleepingProcessPids);
}
}

public function noParentPids(): array
{
$pids = $this->getValidPids();

$allPids = $this->getAllQueueProcessPids();

return array_diff($allPids, $pids);
}

/**
* kill 掉无效的进程
*
* @param array $pids
*/
private function killInvalidProcess(array $pids)
{
foreach ($pids as $pid) {
if (!posix_kill($pid, SIGTERM)) {
Log::error("kill ${pid} error!");
}

if ($error = posix_get_last_error()) {
Log::error('kill process error: ' . posix_strerror($error));
}
// system("kill -9 {$pid}");
}
}

/**
* 从 pid 数组里面筛选出睡眠状态的进程
*
* @param array $pids
*
* @return array
*/
public function getSleepingProcessPids(array $pids): array
{
$result = [];

foreach ($pids as $pid) {
if ($this->isSleeping($pid)) {
$result[] = $pid;
}
}

return $result;
}

/**
* 进程是否是睡眠状态
*
* @param $pid
*
* @return bool
*/
public function isSleeping($pid): bool
{
$res = $this->grepPidStatus($pid);

if ($res) {
$status = $this->statusOfLine($res);
if ($status == 'S') {
return true;
}
}

return false;
}

public function grepPidStatus($pid)
{
return shell_exec("ps -efww | grep php | grep ${pid} | grep -v grep");
}

/**
* 获取当前所有的队列进程
*
* @return array
*/
public function getAllQueueProcessPids(): array
{
$res = $this->horizonProcesses();
$allPids = [];

foreach (explode("\n", $res) as $line) {
if (!$line) {
continue;
}

$allPids[] = $this->pidOfLine($line);
}

return $allPids;
}

public function horizonProcesses()
{
return shell_exec('ps -efww | grep horizon | grep -v grep');
}

/**
* 当前有效的进程 id 数组
*
* @return array
*/
public function getValidPids(): array
{
$horizonPid = $this->horizonProcessPid();
if (!$horizonPid) {
return [];
}

$res = $this->pstree($horizonPid);

preg_match_all("/php,\d+/", $res, $matches);

$pids = [];
if ($matches) {
foreach ($matches[0] as $match) {
$pids[] = substr($match, 4);
}
}

return $pids;
}

public function pstree($horizonPid)
{
return shell_exec("pstree -a {$horizonPid} -p");
}

/**
* 获取 horizon 启动进程 id
*
* @return int|mixed
*/
public function horizonProcessPid()
{
$res = $this->horizonPidLine();

if ($res) {
return $this->pidOfLine($res);
}

return 0;
}

public function horizonPidLine()
{
return shell_exec("ps -efww | grep -v grep | grep '/usr/lnmp/php/bin/php /home/www/api/default/current/artisan horizon'");
}

/**
* 从 ps aux 的输出行里面获取进程状态 S -> 睡眠
*
* @param string $line
*
* @return mixed
*/
public function statusOfLine(string $line)
{
return $this->fields($line)[7];
}

/**
* 从 ps aux 的输出行里面获取 pid
*
* @param string $line
*
* @return mixed
*/
public function pidOfLine(string $line)
{
return $this->fields($line)[1];
}

/**
* ps aux 输出行里面空格分隔的单词列表
*
* @param string $line
*
* @return array
*/
public function fields(string $line): array
{
$line = trim($line);

$fields = explode(' ', $line);

return array_values(array_filter($fields));
}
}

环境规划

集群类型

kubernetes 集群大体上分为两类:一主多从多主多从

  • 一主多从:一台 Master 节点和多台 Node 节点,搭建简单,但是有单机故障风险,适合用于测试环境。
  • 多主多从:多台 Master 节点和多台 Node 节点,搭建麻烦,安全性高,适合用于生产环境。
cluster

说明:为了测试简单,本次搭建的是一主两从类型的集群。

安装方式

kubernetes 有多种部署方式,目前主流的方式有 kubeadm、minikube、二进制包。

  • minikube:一个用于快速搭建单节点 kubernetes 的工具
  • kubeadm:一个用于快速搭建 kubernetes 集群的工具
  • 二进制包:从官网下载每个组件的二进制包,依次去安装,此方式对于理解 kubernetes 组件更加有效

说明:现在需要安装 kubernetes 的集群环境,但是又不想过于麻烦,所以使用 kubeadm 方式

主机规划

作用 IP地址 操作系统 配置
Master 192.168.235.100 CentOS7.5 基础设施服务器 2颗CPU 2G内存 50G硬盘
Node1 192.168.235.101 CentOS7.5 基础设施服务器 2颗CPU 2G内存 50G硬盘
Node2 192.168.235.102 CentOS7.5 基础设施服务器 2颗CPU 2G内存 50G硬盘

环境搭建

本次环境搭建需要安装三台 linux 系统(一主二从),内置 CentOS7.5 系统,然后在每台 linux 中分别安装 docker,kubeadm,kubelet,kubectl 程序。

主机安装

安装虚拟机过程注意下面选项的设置:

  • 操作系统环境:CPU(2C) 内存(2G)硬盘(50G)
  • 语言选择:中文简体
  • 软件选择:基础设施服务器
  • 分区选择:自动分区
  • 网络配置:按照下面配置网络地址信息(IPv4)

网络地址:192.168.235.100(每台主机都不一样 分别为 100、101、102)(vmware的编辑->虚拟网络编辑器里面第二个网卡可以看到本地的虚拟机网段是什么) 子网掩码:255.255.255.0 默认网关:192.168.235.2 DNS:223.5.5.5

vm-network

常规里面的第一个勾上。

  • 主机名设置:按照下面信息设置主机名

master节点:master node节点:node1 node节点:node2

vm-hostname

环境初始化

  1. 检查操作系统的版本
1
2
3
# 此方式安装 kubernetes 集群要求 CentOS 版本在 7.5 或以上
[root@master ~]# cat /etc/redhat-release
CentOS Linux release 7.5.1804 (Core)
  1. 主机名解析

为了方便后面集群节点间的直接调用,再配置一下主机名解析,企业中推荐使用内部 DNS 服务器

1
2
3
4
# 主机名解析 编辑三台服务器的 /etc/hosts 文件,添加下面内容
192.168.235.100 master
192.168.235.101 node1
192.168.235.102 node2
  1. 时间同步

kubernetes 要求集群中的节点时间必须精确一致,这里直接使用 chronyd 服务从网络同步时间。

企业中建议配置内部时间同步服务器。

1
2
3
4
5
6
7
# 启动 chronyd 服务
[root@master ~]# systemctl start chronyd
# 设置 chronyd 服务开机启动
[root@master ~]# systemctl enable chronyd
# chronyd 服务启动稍等几秒钟,就可以使用 date 命令验证时间了
[root@master ~]# date
2021年 11月 27日 星期六 09:28:07 CST
  1. 禁用 iptables 和 firewalld 服务

kubernetes 和 docker 在运行中会产生大量的 iptables 规则,为了不让系统规则跟它们混淆,直接关闭系统的规则

1
2
3
4
5
6
7
8
9
10
11
# 1 关闭 firewalld 服务
[root@master ~]# systemctl stop firewalld
[root@master ~]# systemctl disable firewalld
Removed symlink /etc/systemd/system/multi-user.target.wants/firewalld.service.
Removed symlink /etc/systemd/system/dbus-org.fedoraproject.FirewallD1.service.

# 2 关闭 iptables 服务
[root@master ~]# systemctl stop iptables
Failed to stop iptables.service: Unit iptables.service not loaded.
[root@master ~]# systemctl disable iptables
Failed to execute operation: No such file or directory
  1. 禁用 selinux

selinux 是 linux 系统下的一个安全服务,如果不关闭它,在安装集群中会产生各种各样的奇葩问题。

1
2
3
# 编辑 /etc/selinux/config 文件,修改 SELINUX 的值为 disabled
# 注意修改完毕之后需要重启 linux 服务
SELINUX=disabled
  1. 禁用 swap 分区

swap 分区指的是虚拟内存分区,它的作用是在物理内存使用完之后,将磁盘空间虚拟成内存来使用。

启用 swap 设备会对系统的性能产生非常负面的影响,因此 kubernetes 要求每个节点都要禁用 swap 设备。

但是如果因为某些原因确实不能关闭 swap 分区,就需要在集群安装过程中通过明确的参数进行配置说明。

1
2
3
4
# 编辑分区配置文件 /etc/fstab,注释掉 swap 分区一行
# 注意修改完毕之后需要重启 linux 服务
UUID=0a79fea5-0afa-49a7-9c5e-53c37a76980f /boot xfs defaults 0 0
#/dev/mapper/centos-swap swap swap defaults 0 0
  1. 修改 linux 的内核参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 修改 linux 的内核参数,添加网桥过滤和地址转发功能
# 编辑 /etc/sysctl.d/kubernetes.conf 文件,添加如下配置:
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1

# 重新加载配置
[root@master ~]# sysctl -p

# 加载网桥过滤模块
[root@master ~]# modprobe br_netfilter

# 查看网桥过滤模块是否加载成功
[root@master ~]# lsmod | grep br_netfilter
br_netfilter 22256 0
bridge 146976 1 br_netfilter
  1. 配置 ipvs 功能

在 kubernetes 中 service 有两种代理模型,一种是基于 iptables 的,一种是基于 ipvs 的。

两者比较的话,ipvs 的性能明显要高一些,但是如果要使用它,需要手动载入 ipvs 模块。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 1 安装 ipset 和 ipvsadm
[root@master ~]# yum install ipset ipvsadm -y

# 2 添加需要加载的模块写入脚本文件
[root@master ~]# 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

# 3 为脚本文件添加执行权限
[root@master ~]# chmod +x /etc/sysconfig/modules/ipvs.modules

# 4 执行脚本文件
[root@master ~]# /bin/bash /etc/sysconfig/modules/ipvs.modules

# 5 查看对应的模块是否加载成功
[root@master ~]# lsmod | grep -e ip_vs -e nf_conntrack_ipv4
  1. 重启服务器

上面步骤完成之后,需要重新启动 linux 系统。

安装 docker

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
# 1 切换镜像源
[root@master ~]# wget https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo -O /etc/yum.repos.d/docker-ce.repo

# 2 查看当前镜像源中支持的 docker 版本
[root@master ~]# yum list docker-ce --showduplicates

# 3 安装指定版本的 docker-ce
# 必须指定 --setopt=obsoletes=0,否则 yum 会自动安装更高版本
[root@master ~]# yum install --setopt=obsoletes=0 docker-ce-18.06.3.ce-3.el7 -y

# 4 添加一个配置文件
# Docker 在默认情况下使用 Cgroup Driver 为 cgroupfs,而 kubernetes 推荐使用 systemc 来代替 cgroupfs
[root@master ~]# mkdir /etc/docker
[root@master ~]# cat <<EOF > /etc/docker/daemon.json
{
"exec-opts": ["native.cgroupdriver=systemd"],
"registry-mirrors": ["https://kn0t2bca.mirror.aliyuncs.com"]
}
EOF

# 5 启动 docker
[root@master ~]# systemctl restart docker

# 6 检查 docker 状态和版本
[root@master ~]# docker version

安装 kubernetes 组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 由于 kubernetes 的镜像源在国外,速度比较慢,这里切换成国内的镜像源
# 编辑 /etc/yum.repos.d/kubernetes.repo,添加下面的配置
[kunernetes]
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

# 安装 kubeadm、kubelet 和 kubectl
[root@master ~]# yum install --setopt=obsoletes=0 kubeadm-1.17.4-0 kubelet-1.17.4-0 kubectl-1.17.4-0 -y

# 配置 kubelet 的 cgroup
# 编辑 /etc/sysconfig/kubelet,添加下面的配置
KUBELET_CGROUP_ARGS="--cgroup-driver=systemd"
KUBE_PROXY_MODE="ipvs"

# 设置 kubelet 开机自启
[root@master ~]# systemctl enable kubelet

准备集群镜像

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 在安装 kubernetes 集群之前,必须要提前准备好集群需要的镜像,所需镜像可以通过下面命令查看
[root@master ~]# kubeadm config images list

# 下载镜像
# 此镜像在 kubernetes 的仓库中,由于网络原因,无法连接,下面提供了一种替代方案
images=(
kube-apiserver:v1.17.4
kube-controller-manager:v1.17.4
kube-scheduler:v1.17.4
kube-proxy:v1.17.4
pause:3.1
etcd:3.4.3-0
coredns:1.6.5
)

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
docker rmi registry.cn-hangzhou.aliyuncs.com/google_containers/$imageName
done

集群初始化

下面开始对集群进行初始化,并将 node 节点加入到集群中

下面的操作只需要在 master 节点上执行即可

1
2
3
4
5
6
7
8
9
10
11
# 创建集群
[root@master ~]# kubeadm init \
--kubernetes-version=v1.17.4 \
--pod-network-cidr=10.244.0.0/16 \
--service-cidr=10.96.0.0/12 \
--apiserver-advertise-address=192.168.235.100

# 创建必要文件(注意看初始化成功后的输出)
[root@master ~]# mkdir -p $HOME/.kube
[root@master ~]# cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
[root@master ~]# chown $(id -u):$(id -g) $HOME/.kube/config

下面的操作只需要在 node 节点上执行即可

1
2
3
4
5
6
7
8
9
10
11
12
13
# 将 node 节点加入集群
[root@master ~]# kubeadm join 192.168.235.100:6443 \
--token i50jyb.m7n0z8wxwecqvvs1 \
--discovery-token-ca-cert-hash sha256:51c607f11b6f9077e073ccf9111047c7838b1e8f7ef52690d8c9b3fb6d9b67b3

# 这里 token 和 hash 取值看 master 初始化的输出。

# 查看集群状态 此时集群的状态为 NotReady,这是因为还没有配置网络插件
[root@master ~]# kubectl get nodes
NAME STATUS ROLES AGE VERSION
master NotReady master 5m52s v1.17.4
node1 NotReady <none> 23s v1.17.4
node2 NotReady <none> 2s v1.17.4

安装网络插件

kubernetes 支持多种网络插件,比如 flannel、calico、canal 等等,任选一种使用即可,本次选择 flannel

下面操作依旧只在 master 节点执行即可,插件使用的是 DaemonSet 的控制器,它会在每个节点上都运行

1
2
3
4
5
6
7
8
9
10
11
12
# 获取 flannel 的配置文件
[root@master ~]# wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

# 使用配置文件启动 flannel
[root@master ~]# kubectl apply -f kube-flannel.yml

# 稍等片刻,再次查看集群节点的状态
[root@master ~]# kubectl get nodes
NAME STATUS ROLES AGE VERSION
master Ready master 16m v1.17.4
node1 Ready <none> 11m v1.17.4
node2 Ready <none> 10m v1.17.4

至此,kubernetes 的集群环境搭建完成。

服务部署

接下来在 kubernetes 集群中部署一个 nginx 程序,测试下集群是否在正常工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 部署 nginx
[root@master ~]# kubectl create deployment nginx --image=nginx:1.14-alpine
deployment.apps/nginx created

# 暴露端口
[root@master ~]# kubectl expose deployment nginx --port=80 --type=NodePort
service/nginx exposed

# 查看服务状态
[root@master ~]# kubectl get pods,svc
NAME READY STATUS RESTARTS AGE
pod/nginx-6867cdf567-z6jzl 1/1 Running 0 17s

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 19m
service/nginx NodePort 10.105.209.91 <none> 80:32350/TCP 8s

# 最后在电脑上访问下部署的 nginx 服务
nginx-svc