0%

问题

生产环境使用了 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

Kubernetes 的本质是一组服务器集群,它可以在集群的每个节点上运行特定的程序,来对节点中的容器进行管理。它的目的就是实现资源管理的自动化,主要提供了如下的主要功能:

  • 自我修复:一旦某一个容器崩溃,能够在 1 秒左右迅速启动新的容器。
  • 弹性伸缩:可以根据需要,自动对集群中正在运行的容器数量进行调整。
  • 服务发现:服务可以通过自动发现的形式找到它所依赖的服务。
  • 负载均衡:如果一个服务启动了多个容器,能够自动实现请求的负载均衡。
  • 版本回退:如果发现新发布的程序版本有问题,可以立即回退到原来的版本。
  • 存储编排:可以根据容器自身的需求自动创建存储卷。
node

Kubernetes 组件

一个 Kubernetes 集群主要是由控制节点(master)、工作节点(node)构成,每个节点上都会安装不同的组件。

master:集群的控制平面,负责集群的决策(管理)

  • ApiServer:资源操作的唯一入口,接收用户输入的命令,提供认证、授权、API注册和发现等机制(控制入口)
  • Scheduler:负责集群资源调度,按照预定的调度策略将 Pod 调度到相应的 node 节点上
  • ControllerManager:负责维护集群的状态,比如程序部署安排、故障检测、自动扩展、滚动更新等
  • Etcd:负责存储集群中各种资源对象的信息

简单来说就是,用户操作命令会到达 ApiServer,然后由 Scheduler 计算操作应该发往哪个 node 上进行,计算完毕,由 ControllerManager 与 node 进行最后的交互。

而这个操作的最终结果会存储到 Etcd 里。

node:集群的数据平面,负责为容器提供运行环境(干活)

  • Kubelet:负责维护容器的生命周期,即通过控制 docker,来创建、更新、销毁容器
  • KubeProxy:负责提供集群内部的服务发现和负载均衡(访问入口)
  • Docker:负责节点上容器的各种操作

Kubelet 接收 master 的操作命令,然后控制 Docker 进行具体操作。

容器运行起来之后,由于 KubeProxy 提供集群内部的服务发现和负载均衡。

components

下面,以部署一个 nginx 服务来说明 kubernetes 系统各个组件调用关系:

  1. 首先要明确,一旦 kubernetes 环境启动之后,master 和 node 都会将自身的信息存储到 etcd 数据库中。
  2. 一个 nginx 服务的安装请求会首先被发送到 master 节点的 ApiServer 组件
  3. ApiServer 组件会调用 Scheduler 组件来决定到底应该把这个服务安装到哪个 node 节点上。在此时,它会从 etcd 中读取各个 node 节点的信息,然后 按照一定的算法进行选择,并将结果告知 ApiServer
  4. ApiServer 调用 ControllerManager 去调度 node 节点来安装 nginx 服务
  5. kubelet 接收到指令后,会通知 docker,然后由 docker 来启动一个 nginx 的 pod。pod 是 kubernetes 的最小操作单元,容器必须跑在 pod 中。
  6. 至此,一个 nginx 服务就运行了,如果需要访问 nginx,就需要通过 kube-proxy 来对 pod 产生访问的代理。这样,外界用户就可以访问集群中的 nginx 服务了。

Kubernetes 概念

  • Master:集群控制节点,每个集群需要至少一个 master 节点负责集群的管控。
  • Node:工作负载节点,由 master 分配容器到这些 node 工作节点上,然后 node 节点上的 docker 负责容器的运行。
  • Pod:kubernetes 的最小控制单元,容器都是运行在 pod 中的,一个 pod 可以有 1 个或者多个容器。
  • Controller:控制器,通过它来实现对 pod 的管理,比如启动 pod、停止 pod、伸缩 pod 的数量等等。
  • Service:pod 对外服务的统一入口,下面可以维护着同一类的多个 pod。
  • Label:标签,用于对 pod 进行分类,同一类 pod 会拥有相同的标签。
  • Namespace:命名空间,用来隔离 pod 的运行环境。
concept

应用部署方式演变

  • 传统部署:互联网早期,会直接将应用程序部署在物理机上。
    • 优点:简单,不需要其他技术的参与。
    • 缺点:不能为应用程序定义资源的使用边界,很难合理地分配计算资源,而且应用程序之间容易产生影响。
  • 虚拟化部署:可以在一台物理机上运行多个虚拟机,每个虚拟机都是独立的一个环境。
    • 优点:程序环境不会相互产生影响,提供了一定程度的安全性。
    • 缺点:增加了操作系统,浪费了部分资源。
  • 容器化部署:与虚拟化类似,但是共享了操作系统。
    • 优点:
      • 可以保证每个容器拥有自己的文件系统、CPU、内存、进程空间等。
      • 运行应用程序所需要的资源都被容器包装,并和底层基础架构解耦。
      • 容器化的应用程序可以跨云服务商、跨 Linux 操作系统发行版进行部署。
deployment

容器化部署的问题

  • 一个容器故障了,怎么样让另外一个容器立刻启动去替补停机的容器。
  • 当并发访问量变大的时候,怎么样做到横向扩展容器数量。

这些容器管理的问题统称为容器编排问题,为了解决这些容器编排问题,就产生了一些容器编排的软件:

  • Swarm:Docker 自己的容器编排工具。
  • Mesos:Apache 的一个统一管控工具,需要和 Marathon 结合使用
  • Kubernetes:Google 开源的容器编排工具。

禁止 ip 1.2.3.4 访问服务器

1
2
firewall-cmd --permanent --add-rich-rule="rule family=ipv4 source address=1.2.3.4 drop"
firewall-cmd --reload

最后一个 drop 的效果,不返回结果给请求地址,所以客户端会等待直至超时。

reject/drop 说明

移除 ip 禁止访问规则

1
2
firewall-cmd --permanent --remove-rich-rule 'rule family="ipv4" source address="1.2.3.4" drop'
firewall-cmd --reload

列出所有规则

1
firewall-cmd --list-all

reject 和 drop 的区别

在firewalld中,DROP很简单就是直接丢弃数据,并不反馈任何回应。需要Client等待超时,Client容易发现自己被防火墙所阻挡。而REJECT则会更为礼貌的返回一个拒绝(终止)数据包(TCP FIN或UDP-ICMP-PORT-UNREACHABLE),明确的拒绝对方的连接动作。连接马上断开,Client会认为访问的主机不存在。

至于使用DROP还是REJECT更合适请大家根据自己的使用场景进行选择, REJECT是一种更符合规范的处理方式,并且在可控的网络环境中,更易于诊断和调试网络/防火墙所产生的问题;而DROP则提供了更高的防火墙安全性和少许的效率提高,但是由于DROP不很规范(不很符合TCP连接规范)的处理方式,可能会对你的网络造成一些不可预期或难以诊断的问题。因为DROP虽然单方面的中断了连接,但是并不返回任何拒绝信息,因此连接客户端将被动的等到tcp session超时才能判断连接是否成功。

在部署防火墙时,如果是面向企业内部(或部分可信任网络),那么最好使用规范的REJECT方法,对于需要经常变更或调试规则的网络也是如此;而对于面向危险的Internet/Extranet的防火墙,则有必要使用更为粗暴但是安全的DROP方法,可以在一定程度上延缓攻击的进度或难度。