LJKのBlog

学无止境

实验目的

搭建 DNS 正向主服务器,实现 web 服务器基于 FQDN 的访问

环境要求

需要三台主机
DNS 服务端:10.0.0.175
web 服务器:10.0.0.8
DNS 客户端:10.0.0.57

提前准备

关闭 SElinux
关闭防火墙
时间同步

实现步骤

1. 在 DNS 服务端安装 bind

1
[root@centos8 ~]$yum -y install bind

2. 修改 bind 配置文件

修改配置文件 /etc/named.conf

1
2
3
#注释掉下面两行
// listen-on port 53 { 127.0.0.1; };
// allow-query { localhost; };

修改配置文件 vim /etc/named.rfc1912.zones

1
2
3
4
5
#加上下面内容, IN 可以省略
zone "magedu.local" IN {
type master;
file "magedu.local.zone";
};

3. DNS 区域数据库文件

在主目录(默认是/var/named/)下新建 DNS 区域数据库文件 magedu.local.zone

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
#如果没有加-p选项,需要修改所有者或权限。chgrp named magedu.local.zone
[root@centos8 ~]$cd /var/named/
[root@centos8 named]$cp -p named.localhost magedu.local.zone
[root@centos8 named]$ll
total 32
drwxrwx--- 2 named named 4096 Sep 17 22:55 data
drwxrwx--- 2 named named 4096 Sep 17 21:49 dynamic
-rw-r----- 1 root named 152 Jul 7 22:14 magedu.local.zone
-rw-r----- 1 root named 2253 Jul 7 22:14 named.ca
-rw-r----- 1 root named 152 Jul 7 22:14 named.empty
-rw-r----- 1 root named 152 Jul 7 22:14 named.localhost
-rw-r----- 1 root named 168 Jul 7 22:14 named.loopback
drwxrwx--- 2 named named 4096 Jul 7 22:14 slaves
[root@centos8 named]$vim magedu.local.zone
# 修改magedu.local.zone为以下内容:
$TTL 1D
@ IN SOA master admin.magedu.org. ( ;master 会自动补全 master.magedu.local
0 ; serial
1D ; refresh
1H ; retry
1W ; expire
3H ) ; minimum
NS master
master A 10.0.0.175
www A 10.0.0.8

4. 检查配置文件和数据库文件格式,并启动服务

1
2
3
4
5
6
7
8
[root@centos8 named]$named-checkconf
[root@centos8 named]$named-checkzone magedu.local.zone /var/named/magedu.local.zone
zone magedu.local.zone/IN: loaded serial 0
OK
# systemctl start named 第一次启动服务
# rndc reload 不是第一次启动服务
[root@centos8 named]$rndc reload
server reload successful

5. 实现 WEB 服务

在 web 服务器 10.0.0.8 执行以下命令:

1
echo 'www.magedu.local' > /var/www/html/index.html

6. 在客户端实现测试

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
# 添加DNS从服务器ip到/etc/resolv.conf
[root@centos7 ~]$cat /etc/resolv.conf
# Generated by NetworkManager
search magedu.org
nameserver 10.0.0.175

# dig
[root@centos7 ~]$dig www.magedu.local

; <<>> DiG 9.11.4-P2-RedHat-9.11.4-16.P2.el7_8.6 <<>> www.magedu.local
;; global options: +cmd
;; Got answer:
;; WARNING: .local is reserved for Multicast DNS
;; You are currently testing what happens when an mDNS query is leaked to DNS
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 48252
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 2

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;www.magedu.local. IN A

;; ANSWER SECTION:
www.magedu.local. 86400 IN A 10.0.0.8

;; AUTHORITY SECTION:
magedu.local. 86400 IN NS master.magedu.local.

;; ADDITIONAL SECTION:
master.magedu.local. 86400 IN A 10.0.0.175

;; Query time: 1 msec
;; SERVER: 10.0.0.175#53(10.0.0.175)
;; WHEN: Thu Sep 17 19:11:55 CST 2020
;; MSG SIZE rcvd: 98

# 能ping通
[root@centos7 ~]$ping www.magedu.local
PING www.magedu.local (10.0.0.8) 56(84) bytes of data.
64 bytes from 10.0.0.8 (10.0.0.8): icmp_seq=1 ttl=64 time=0.549 ms
64 bytes from 10.0.0.8 (10.0.0.8): icmp_seq=2 ttl=64 time=0.664 ms
64 bytes from 10.0.0.8 (10.0.0.8): icmp_seq=3 ttl=64 time=0.838 ms
^C
--- www.magedu.local ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 0.549/0.683/0.838/0.122 ms

# 能访问
[root@centos7 ~]$curl www.magedu.local
www.magedu.local

查看 web 服务器查的 http 访问记录:

1
2
3
[root@centos8 httpd]$cat /dev/null > access_log
[root@centos8 httpd]$tail -f access_log
10.0.0.57 - - [17/Sep/2020:07:47:55 -0400] "GET / HTTP/1.1" 200 17 "-" "curl/7.29.0"

DNS 相关概念和技术

DNS 查询类型

  • 递归查询:一般客户机和本地 DNS 服务器之间属于递归查询。如果本地 DNS 服务器本身不能解析,则本地 DNS 服务器向其他 DNS 服务器发起发出查询请求。
  • 迭代查询:一般情况下(有例外)本地的 DNS 服务器向其它 DNS 服务器的查询属于迭代查询。如果其他 DNS 服务器也不能解析,则本地 DNS 服务器再向下一个 DNS 服务器发出查询请求。

名称服务器

Name Server:域内负责解析本域内的名称的 DNS 服务器

完整的查询请求经过的流程

1
Client -->hosts文件 --> Client DNS Service Local Cache --> DNS Server (递归) --> DNS Server Cache -->DNS iteration(迭代) --> 根--> 顶级域名DNS-->二级域名DNS…

DNS 服务器类型

  • 主 DNS 服务器

    管理和维护所负责解析的域内解析库的服务器

  • 从 DNS 服务器

    从主服务器或从服务器“复制”(区域传输)解析库副本。

    • 序列号:解析库版本号,主服务器解析库变化时,其序列递增
    • 刷新时间间隔:从服务器从主服务器请求同步解析的时间间隔
    • 重试时间间隔:从服务器请求同步失败时,再次尝试时间间隔
    • 过期时长:从服务器联系不到主服务器时,多久后停止服务
    • 通知机制:主服务器解析库发生变化时,会主动通知从服务器
  • 缓存 DNS 服务器(转发器)

区域传输

  • 完全传输:传送整个解析库
  • 增量传输:传递解析库变化的那部分内容

解析类型

  • FQDN( Fully Qualified Domain Name) –> IP:正向解析
  • IP –> FQDN:反向解析

注意:正反向解析是两个不同的名称空间,是两棵不同的解析树

负责本地域名的正向和反向解析库

  • 正向区域
  • 反向区域

解析答案

  • 肯定答案:存在对应的查询结果
  • 否定答案:请求的条目不存在等原因导致无法返回结果
  • 权威答案:直接由存有此查询结果的 DNS 服务器(权威服务器)返回的答案
  • 非权威答案:由其它非权威服务器返回的查询答案

资源记录定义格式

1
name [TTL] IN rr_type value
  • name:zone 的名称,是一个 FQDN,通常使用@来表示;zone 的名称可以定义变量$ORIGIN来表示,优先继承$ORIGIN
  • TTL:可从全局继承
  • IN:表示记录类型属于 Internet 类别
  • rr_type:resource record 资源记录
  • value:根据资源记录类型的不同,value 也不同

同一个名字可以通过多条记录定义多个不同的值;此时 DNS 服务器会以轮询方式响应

同一个值也可能有多个不同的定义名字;通过多个不同的名字指向同一个值进行定义;此仅表示通过多个不同的名字可以找到同一个主机

各种资源记录

参考:https://blog.csdn.net/qq_26711103/article/details/82873550

区域解析库:由众多资源记录 RR(Resource Record)组成

SOA

Start Of Authority:起始授权记录,一个区域解析库有且仅能有一个 SOA 记录,必须位于解析库的第一条记录

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
; zone file fragment for mytest.cn
;$TTL 600
$ORIGIN mytest.cn.
; SOA record
; owner-name ttl class rr name-server email-addr (sn ref ret ex min)
@ IN SOA ns1.mytest.cn. root.mytest.cn. (
0 ; serial
1D ; refresh
1H ; retry
1W ; expire
3H ) ; minimum

# M:分钟;H:小时;D:天;W:周
  • owner-name:当前域的名称,通常用 @ 来表示,优先继承$ORIGIN

  • value:可以设置多个值,本例中设置了三个值,依次为:

    1. name-server:当前域的主 DNS 服务器的 FQDN,形式为 xxx.ower-name,ower-name 可以省略,例如这里ns1.mytest.cn.还可以写成ns1,下面得配合一条 A 记录把它解析成 ip

    2. email-addr:负责此区域的人员的电子邮件地址,因为@在这里有特殊意义,所以用.替代。所以上面的root.mytest.cn.实际上是root.mytest@cn.

    3. 主从服务区域传输相关定义

      1. serial
        序列号 – Serial,每次变更区域内容时数值+1,以通知 slave 同步数据。值范围 1 ~ 4294967295,最大增量 2147483647
      2. refresh
        更新频率 – Refresh,slave 主动向 master 更新。建议 1200 ~ 43200 秒
      3. retry
        重试时间 – Retry,当 slave 同步数据失败,多少时间内会再次重试同步。典型值为 180(3 分钟)至 900(15 分钟)或更高。
      4. expire
        失效时间(Expire),一直尝试的失败时间,持续到这个设定值,指示区域数据不再具有权威性。建议 1209600 ~ 2419200 秒 (2-4 weeks)
      5. minimum
        bind9 开始将此值重新定义为负缓存时间。任何解析器都可以缓存 NAME ERROR = NXDOMAIN 结果的时间。允许的最大值是 3 hours (10800 seconds).

NS

Name Server,专用于标明当前区域的 DNS 服务器,范例:

1
2
3
4
5
# name [TTL] IN rr_type value
# name: 当前区域的名字
# value: 当前区域的某DNS服务器的名字,例如ns.magedu.org
magedu.org. IN NS ns1.magedu.org.
magedu.org. IN NS ns2.magedu.org.

注意:

  1. 相邻的两个资源记录的 name 相同时,后续的可省略
  2. 对 NS 记录而言,任何一个 ns 记录后面的服务器名字,都应该在后续有一个 A 记录
  3. 一个区域可以有多个 NS 记录

MX

Mail eXchanger,邮件交换器,范例:

1
2
3
4
5
6
7
# name [TTL] IN rr_type value
# name: 当前区域的名字
# value: 当前区域的某邮件服务器(smtp服务器)的主机名
magedu.org. IN MX 10 mx1.magedu.org.
IN MX 20 mx2.magedu.org.
mx1 A 10.0.0.100
mx2 A 10.0.0.200

注意:

  1. 一个区域内,MX 记录可有多个;但每个记录的 value 之前应该有一个数字(0-99),表示此服务器的优先级;数字越小优先级越高
  2. 对 MX 记录而言,任何一个 MX 记录后面的服务器名字,都应该在后续有一个 A 记录

A

internet Address,作用:FQDN –> IP,范例:

1
2
3
4
5
6
7
8
9
10
# name: 某主机的FQDN,例如:www.magedu.org.
# value: 主机名对应主机的IP地址
# 避免用户写错名称时给错误答案,可通过泛域名解析进行解析至某特定地址
www.magedu.org. IN A 1.1.1.1
www.magedu.org. IN A 2.2.2.2
mx1.magedu.org. IN A 3.3.3.3
mx2.magedu.org. IN A 4.4.4.4
$GENERATE 1-254 HOST$ IN A 1.2.3.$
*.magedu.org. IN A 5.5.5.5
magedu.org. IN A 6.6.6.6

AAAA

FQDN –> IPv6

1
2
name: FQDN
value: IPv6

PTR

PoinTeR,IP –> FQDN,范例:

1
2
3
4
5
# name: IP,有特定格式,把IP地址反过来写,1.2.3.4,要写作4.3.2.1;而有特定后缀:in-addr.arpa.,所以完整写法为:4.3.2.1.in-addr.arpa.
# value: FQDN
4.3.2.1.in-addr.arpa. IN PTR www.magedu.org.
# 网络地址及后缀可省略;主机地址依然需要反着写,如1.2.3为网络地址,可简写成:
4 IN PTR www.magedu.org.

CNAME

Canonical Name,别名记录,范例:

1
2
3
# name: 别名的FQDN
# value: 真正名字的FQDN
www.magedu.org. IN CNAME websrv.magedu.org.

TXT

对域名进行标识和说明的一种方式,一般做验证记录时会使用此项,如:SPF(反垃圾邮件)记录,https 验证等。范例:

1
_dnsauth TXT 2012011200000051qgs69bwoh4h6nht4n1h0lr038x

子域授权

每个域的名称服务器,都是通过其上级名称服务器在解析进行授权。

glue record:粘合记录,父域授权子域的记录。

范例:

1
2
3
4
.com. IN NS ns1.com.
.com. IN NS ns2.com.
ns1.com. IN A 2.2.2.1
ns2.com. IN A 2.2.2.

互联网域名

注册,备案,解析。。。

DNS 软件 bind

DNS 服务器软件:bind、powerdns、dnsmasq、unbound、coredns

bind 相关程序包

  • bind:服务端
  • bind-libs:相关库
  • bind-utils:客户端
  • bind-chroot:安全包,默认将 dns 相关文件放至/var/named/chroot
1
[root@centos8 ~]#dnf -y install bind bind-utils

bind 相关文件

  • bind 主程序:/usr/sbin/named。bind 的主程序是 named,不同名,有点小奇怪
  • 服务脚本和 Unit 名称:/etc/rc.d/init.d/named、/usr/lib/systemd/system/named.service
  • 主配置文件:/etc/named.conf、etc/named.rfc1912.zones、/etc/rndc.key
  • 管理工具:/usr/sbin/rndc:remote name domain controller,默认与 bind 安装在同一主机,且只能通过 127.0.0.1 连接 name 进程,提供辅助性的管理功能,953/tcp
  • 解析库文件:/var/named/ZONE_NAME.ZONE
    • 一台物理服务器可同时为多个区域提供解析
    • 必须要有根区域文件:named.ca
    • 应该有两个(如果是 ipv6,则更多)实现 localhost 和本地回环地址的解析库

主配置文件

  • 全局配置:options {}

  • 日志子系统配置:logging {}

  • 区域配置:本机能够为哪些 zone 进行解析,就要被定义哪些 zone

    1
    zone “ZONE_NAME” IN {}

注意:

  • 任何服务器程序如果期望能够通过网络被其他主机访问,至少应该监听在一个能与外部主机通信的 IP 地址上
  • 缓存名称服务器配置:监听外部地址即可
  • dnssec:建议关闭 dnssc,设为 no

实现 DNS 服务器

主 DNS 服务器配置

1. 定义区域

主 DNS 服务器配置文件:/etc/named.conf,示例:

1
2
3
4
5
6
7
8
9
#注释掉下面两行
// listen-on port 53 { 127.0.0.1; };
// allow-query { localhost; };

# 添加区域
zone "ZONE_NAME" IN {
type {master| slave | hint | forward};
file "ZONE_NAME.zone";
};
  • listen-on port:监听的地址和端口。注释掉和将127.0.0.1;修改为localhost;效果是一样的,设置127.0.0.1则只会监听地址 127.0.0.1,而注释掉或者设置为 localhost 则监听所有本机地址
  • allow-query:允许谁向此 DNS 进行查询。注释掉和修改localhost;any;效果是一样的,设置localhost则允许本机的 ip 地址列表

2. 定义区域解析库文件

默认位置:/var/named/目录下;

内容包括两部分:宏定义 和 资源记录,范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$TTL 86400
$ORIGIN magedu.org.
@ IN SOA ns1.magedu.org. admin.magedu.org (
2015042201
1H
5M
7D
1D )
IN NS ns1
IN NS ns2
IN MX 10 mx1
IN MX 20 mx2
ns1 IN A 172.16.100.11
ns2 IN A 172.16.100.12
mx1 IN A 172.16.100.13
mx2 IN A 172.16.100.14
websrv IN A 172.16.100.11
websrv IN A 172.16.100.12
www IN CNAME websrv

3. 主配置文件语法检查

1
[root@centos8 ~]$named-checkconf

4. 主配置文件语法检查

1
named-checkzone [options] zonename filename
1
[root@centos8 ~]$named-checkzone "magedu.org" /var/named/magedu.org.zone

5. 配置生效

三种方式:

  • rndc reload
  • systemctl reload named
  • service named reload

测试和管理工具

dig
1
yum install bind-utils

dig 只用于测试 dns 系统,不会查询 hosts 文件进行解析

1
dig [-t type] name [@SERVER] [query options]
  • -t:要查询的资源记录类型。默认 A,除了 AAAA、NS 这一类,还可以是 axfr(全区域传输) 和 ixfr(增量区域传输)。

  • query options:

    +[no]trace:跟踪解析过程 : dig +trace magedu.org +[no]recurse:进行递归解析

host
1
host [-t type] name [SERVER]

范例:

1
2
3
4
5
host -t NS magedu.org 172.16.0.1
host -t soa magedu.org
host -t mx magedu.org
host -t axfr magedu.org
host 1.2.3.4
nslookup
1
yum install bind-utils

nslookup 可以支持交互和非交互式两种方式执行

1
nslookup [-option] [name | -] [server]

交互模式:

1
2
3
4
nslookup>
server IP: 指明使用哪个DNS server进行查询
set q=RR_TYPE: 指明查询的资源记录类型
NAME: 要查询的名称
rndc

利用 rndc 工具可以实现管理 DNS 功能,rndc 监听端口: 953/tcp

1
rndc COMMAND
  • status: 查看状态
  • reload: 重载主配置文件和区域解析库文件
  • reload zonename: 重载区域解析库文件
  • retransfer zonename: 手动启动区域传送,而不管序列号是否增加
  • notify zonename: 重新对区域传送发通知
  • reconfig: 重载主配置文件
  • querylog: 开启或关闭查询日志文件/var/log/message
  • trace: 递增 debug 一个级别
  • trace LEVEL: 指定使用的级别
  • notrace:将调试级别设置为 0
  • flush:清空 DNS 服务器的所有缓存记录

实战案例:实现 DNS 正向服务器

允许动态更新

动态更新:可以通过远程更新区域数据库的资源记录,例如添加删除解析记录

实现动态更新,需要在指定的 zone 语句块中添加以下指令:

1
Allow-update {any;};

实现反向解析区域

实现从服务器

只有一台主 DNS 服务器,存在单点失败的问题,可以建立主 DNS 服务器的备份服务器,即从服务器来实现 DNS 服务的容错机制。从服务器可以自动和主服务器进行单向的数据同步,从而和主 DNS 服务器一样,也可以对外提供查询服务,但从服务器不提供数据更新服务。

DNS 从服务器

  1. 应该为一台独立的名称服务器
  2. 主服务器的区域解析库文件中必须有一条 NS 记录指向从服务器
  3. 从服务器只需要定义区域,而无须提供解析库文件;解析库文件应该放置于/var/named/slaves/目录中
  4. 主服务器得允许从服务器作区域传送
  5. 主从服务器时间应该同步,可通过 ntp 进行
  6. bind 程序的版本应该保持一致;否则,应该从高,主低

定义从区域

格式:

1
2
3
4
5
zone "ZONE_NAME" IN {
type slave;
masters { MASTER_IP; };
file "slaves/ZONE_NAME.zone";
};

实战案例:实现 DNS 从服务器

实现子域

将子域委派给其他主机管理,实现分布 DNS 数据库

正向解析 DNS 区域子域方法,很简单,只需要配置两条资源记录,一条 NS,一条 A,范例:

1
2
3
4
5
6
7
8
shanghai.magedu.local.        IN  NS  ns1.shanghai.magedu.local.
shanghai.magedu.local. IN NS ns2.shanghai.magedu.local.
zhengzhou.magedu.local. IN NS ns1.zhengzhou.magedu.local.
zhengzhou.magedu.local. IN NS ns2.zhengzhou.magedu.local.
ns1.shanghai.magedu.local. IN A 1.1.1.1
ns2.shanghai.magedu.local. IN A 1.1.1.2
ns1.zhengzhou.magedu.local. IN A 1.1.1.3
ns2.zhengzhou.magedu.local. IN A 1.1.1.4

范例:实现 DNS 父域和子域服务

实现 DNS 转发(缓存)服务器

利用 DNS 转发,可以将用户的 DNS 请求,转发至指定的 DNS 服务器,而非默认的根 DNS 服务器,并将指定服务器查询的返回结果进行缓存,提高效率

注意:被转发的服务器需要能够为请求者做递归,否则转发请求不予执行

转发方式有两种:全局转发 和 特定区域转发

全局转发:对非本机所负责解析区域的请求,全转发给指定的服务器

特定区域转发:仅转发对特定的区域的请求,比全局转发优先级高

1
2
3
4
5
# 无论全局转发还是特定区域转发,都需要关闭dnssec功能
options {
dnssec-enable no;
dnssec-validation no;
};
1
2
3
4
5
6
7
8
9
10
11
12
# 全局转发
options {
forward first|only;
forwarders { ip;};
};

# 特定区域转发
zone "ZONE_NAME" IN {
type forward;
forward first|only;
forwarders { ip;};
};
  • first:先转发至指定 DNS 服务器,如果无法解析查询请求,则本服务器再去根服务器查询
  • only:先转发至指定 DNS 服务器,如果无法解析查询请求,则本服务器将不再去根服务器查询

[实战案例:实现 DNS forward(缓存)服务器](实验 4:DNS forward(缓存)服务器.md)

智能 DNS

智能 DNS 相关技术

ACL

ACL:把一个或多个地址归并为一个集合,并通过一个统一的名称调用,简单说就是对发起 dns 解析请求的客户端的 ip 进行分类,通常会根据省市地区进行分类

注意:只能先定义后使用;因此需要定义在 options 前面

格式:

1
2
3
4
5
6
7
8
acl acl_name {
# 可以是具体ip,也可以网段
ip;
net/prelen;
...
};
...
options {...}

bind 内置 4 个 acl:none、any、localhost、localnet

  • none:没有一个主机
  • any:任意主机
  • localhost:本机
  • localnet:本机的 IP 同掩码运算后得到的网络地址

访问控制指令

  • allow-query {}: 允许查询的主机;白名单
  • allow-transfer {}:允许区域传送的主机;白名单
  • allow-recursion {}: 允许递归的主机,建议全局使用
  • allow-update {}: 允许更新区域数据库中的内容

{}中填入的就是 acl,范例:

1
allow-query { localhost; };  # 只处理来自本机的dns解析请求

view

视图,将 ACL 和区域数据库实现对应关系,以实现智能 DNS

view 格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 一个bind服务器可定义多个view,每个view只能匹配一个acl
# 客户端请求到达时,自上而下检查view中的acl
# 当请求者的ip能被acl匹配到,则使用所在view中的zone数据库,处理其dns解析请求
view VIEW_NAME {
match-clients { acl_name; }; # match-clients 只能匹配一个acl
zone "ZONE_NAME" {
type master;
file "ZONE_FILE";
};
include "/etc/named.rfc1912.zones";
};

# 来自上海的ip,则匹配到对应的zone数据库
view VIEW_NAME {
match-clients { shanghainet; }; # shanghainet是已经定义的acl
zone "magedu.local" {
type master;
file "magedu.local.zone.sh";
};
include "/etc/named.rfc1912.zones";
};
  • ACL 定义在 options 之前,view 定义在 options 之后
  • 一旦启用了 view,所有的 zone 都只能定义在 view 中
  • 仅在允许递归请求的客户端所在 view 中定义根区域

实战案例:利用 view 实现智能 DNS

借助智能 DNS 实现的技术

GSLB

Global Server Load Balance 全局负载均衡

GSLB 是对服务器和链路进行综合判断来决定由哪个地点的服务器来提供服务,实现异地服务器群服务质量的保证

GSLB 主要的目的是在整个网络范围内将用户的请求定向到最近的节点(或者区域)

GSLB 分为基于 DNS 实现、基于重定向实现、基于路由协议实现,其中最通用的是基于 DNS 解析方式

CDN

  1. 用户向浏览器输入www.a.com这个域名,浏览器第一次发现本地没有dns缓存,则向网站的DNS服务器请求

  2. 网站的 DNS 域名解析器设置了 CNAME,指向了www.a.tbcdn.com,请求指向了CDN网络中的智能DNS负载均衡系统

  3. 智能 DNS 负载均衡系统解析域名,把对用户响应速度最快的 IP 节点返回给用户;

  4. 用户向该 IP 节点(CDN 服务器)发出请求

  5. 由于是第一次访问,CDN 服务器会通过 Cache 内部专用 DNS 解析得到此域名的原 web 站点 IP,向原站点服务器发起请求,并在 CDN 服务器上缓存内容

  6. 请求结果发给用户

DNS 排错

1
2
SERVFAIL:The nameserver encountered a problem while processing the query.
# 可使用dig +trace排错,可能是网络和防火墙导致
1
2
NXDOMAIN:The queried name does not exist in the zone.
# 可能是CNAME对应的A记录不存在导致
1
2
REFUSED:The nameserver refused the client's DNS request due to policy restrictions.
# 可能是DNS策略导致

实战案例

综合案例:实现 inertnet 的 DNS 服务架构

引用计数

  1. 每个内存对象都分配一个计数器,当内存对象被变量引用时,计数器+1
  2. 当变量引用撤掉后,计数器-1
  3. 定期检查各内存对象的计数器,如果计数器=0,表明内存对象没有被使用,该内存对象则进行销毁
  4. 垃圾回收完成

四色标记

示例:

1
2
3
4
5
<?php
$a = []; # 计数器 = 1
$a[] = &$a; # 计数器 = 2

unset($a); # 计数器 = 1

对象和数组,有循环引用的问题,针对这两种数据类型,采用四色标记法进行垃圾回收

黑色:正常数据
紫色:疑似垃圾
灰色:
白色:垃圾

  1. 将计数器 -1 过并且目前不为 0 的数组放到缓冲区,并将其标记为紫色
  2. 对缓冲区中的数组进行深度遍历,将紫色的元素标记为灰色,并计数器 -1
    注意:只对元素进行标灰、-1 操作
  3. 再次深度扫描,检查灰色的元素,如果计数器不为 0,将其标记为黑色并计数器+1(上一步有-1 操作),如果计数器为 0,将其标记为白色
  4. 扫描数组,黑色的元素从缓冲区中移除,白色的元素计数器+1 并移到 to_free 列表
  5. 清除 to_free,完成垃圾回收

简单说:将疑似垃圾的数组的元素的计数器-1,=0 说明为垃圾,≠0 说明不是垃圾

JAVA 基础

Java 动态网页技术

servlet

就是前端代码和 java 代码混合的 java 文件,hello.java 示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class HelloWorld extends HttpServlet {
private String message;

public void init() throws ServletException {
message = "Hello World";
}

public void doGet(HttpServletRequest request,HttpServletResponse response)throws ServletException, IOException {
response.setContentType("text/html"); //响应报文内容类型
PrintWriter out = response.getWriter(); //构建响应报文内容
out.println("<h1>" + message + "</h1>");
out.println("<p><a href=http://blog.lujinkai.cn/>LJKのBlog</a>欢迎你</p>");
}

public void destroy(){}

}

jsp (Java Server Pages)

jsp 是 html 模板,后缀是 jsp,hello.jsp 示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>jsp示例</title>
</head>
<body>
<%
out.println("你的 IP 地址 " + request.getRemoteAddr());
%>
</body>
</html>

浏览器访问 jsp 文件:

  1. tomcat 会将 .jsp 文件转为 .java 文件(servlet 类文件)
  2. servlet 类文件转为字节码文件(class 文件),
  3. jvm 解析执行 class 文件

例如:

1
hello.jsp --> hello.java --> hello.class --> jvm执行hello.class

JDK

JRE:Java Runtime Environment,指 Java 运行时环境, 包含 JVM + Java 核心类库
JDK:Java Development Kit,即 Java 语言的软件开发工具包,JDK 协议基于 JRL(JavaResearch License)协议

简单地说,JRE 就是运行 Java 字节码的虚拟机。但是,如果只有 Java 源码,要编译成 Java 字节码,就需要 JDK,因为 JDK 除了包含 JRE,还提供了编译器、调试器等开发工具。

Oracle JDK

JDK 有很多版本,Oracle JDK 是官方版,也是用的最多的

Oracle JDK 早期称为 J2SE(标准版)、J2EE(企业版)、J2ME(微型版),现在叫做 JavaEE

早期的 Oracle JDK 的版本以 1.x 的方式命令:JDK 1.0、JDK 1.1、JDK 1.2、JDK 1.3、JDK 1.4

2004 年 9 月 30 日 18:00PM,J2SE1.5 发布,是 Java 语言的发展史上的又一里程碑事件。为了表示这个版本的重要性,J2SE1.5 更名为 J2SE5.0

2005 年 6 月,JavaOne 大会召开,SUN 公司公开 Java SE 6。此时,Java 的各种版本已经更名以取消其中的数字”2”:J2EE 更名为 Java EE, J2SE 更名为 Java SE,J2ME 更名为 Java ME

Open JDK

Oracle JDK 收费,OpenJDK 是第三方版本的 JDK 中最常用的

OpenJDK 7 是基于 JDK7 的 beta 版开发,但为了也将 Java SE 6 开源,从 OpenJDK7 的 b20 构建反向分支开发,从中剥离了不符合 Java SE 6 规范的代码,发布 OpenJDK 6。所以 OpenJDK6 和 JDK6 没什么关系,只是 API 兼容而已

相对来说,Oracle jDK 具有更好的响应能力和 JVM 性能,更加稳定

Tomcat 基础

tomcat 既是轻量级 web 服务器,可以处理 http 请求,又是 servlet 容器,可以处理 java 程序

tomcat 使用 java 开发,所以依赖 JRE 或者 JDK 环境

安装/升级/回滚

安装

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
# 下载二进制包
[root@c71 src]$wget https://mirrors.bfsu.edu.cn/apache/tomcat/tomcat-8/v8.5.61/bin/apache-tomcat-8.5.61.tar.gz
# 无需编译,解压即可使用
[root@c71 src]$tar zxf apache-tomcat-8.5.61.tar.gz
[root@c71 src]$mv apache-tomcat-8.5.61 /usr/local/tomcat
# 创建tomcat用户
[root@c71 src]$useradd -r -s /sbin/nologin tomcat
[root@c71 src]$chown -R tomcat:tomcat /usr/local/tomcat/
[root@c71 src]$cd /usr/local/tomcat/
[root@c72 bin]$ll catalina.sh startup.sh shutdown.sh
-rwxr-x--- 1 tomcat tomcat 25121 Jan 30 17:19 catalina.sh # 管理tomcat脚本
-rwxr-x--- 1 tomcat tomcat 1902 Jan 30 17:19 shutdown.sh # 停止tomcat脚本
-rwxr-x--- 1 tomcat tomcat 1904 Jan 30 17:19 startup.sh # 启动tomcat脚本
# 提供tomcat所需的环境变量
[root@c71 tomcat]$echo "JAVA_HOME=/usr/local/jdk" >/usr/local/tomcat/conf/tomcat.conf
# 创建tomcat.service文件
[root@c71 tomcat]$vim /usr/lib/systemd/system/tomcat.service
[Unit]
Description=Tomcat
#After=syslog.target network.target remote-fs.target nss-lookup.target
After=syslog.target network.target

[Service]
Type=forking
EnvironmentFile=/usr/local/tomcat/conf/tomcat.conf
ExecStart=/usr/local/tomcat/bin/startup.sh
ExecStop=/usr/local/tomcat/bin/shutdown.sh
PrivateTmp=true
User=tomcat
Group=tomcat

[Install]
WantedBy=multi-user.target
# 设置开机自启
[root@c71 tomcat]$systemctl daemon-reload
[root@c71 tomcat]$systemctl enable --now tomcat

升级和回滚

安装新的 tomcat 版本,然后删除旧的软连接,创建新的软连接,最后重启服务

tomcat 的文件结构和组成

目录 说明
bin 服务启动、停止等相关程序和文件
conf 配置文件
lib 库目录
logs 日志目录
webapps 应用程序,应用部署目录
work jsp 编译后的结果文件,建议提前预热访问

配置文件

文档:http://tomcat.apache.org/tomcat-8.5-doc/index.html

文件名 说明
server.xml 主配置文件
web.xml 每个 webapp 只有“部署”后才能被访问,它的部署方式通常由 web.xml 进行定义,其存放位置为 WEB-INF/目录中;此文件为所有的 webapps 提供默认部署相关的配置,每个 web 应用也可以使用专用配置文件,来覆盖全局文件
context.xml 用于定义所有 web 应用均需加载的 Context 配置,此文件为所有的 webapps 提供默认配置,每个 web 应用也可以使用自已专用的配置,它通常由专用的配置文件 context.xml 来定义,其存放位置为 WEB-INF/目录中,覆盖全局的文件
tomcat-users.xml 用户认证的账号和密码文件
catalina.policy 当使用 security 选项启动 tomcat 时,用于为 tomcat 设置安全策略
catalina.properties Tomcat 环境变量的配置,用于设定类加载器路径,以及一些与 JVM 调优相关参数
logging.properties Tomcat 日志系统相关的配置,可以修改日志级别和日志路径等

注意:配置文件大小写敏感

日志

参考文档: https://cwiki.apache.org/confluence/display/TOMCAT/Logging
日志格式: https://tomcat.apache.org/tomcat-9.0-doc/config/valve.html#Access_Logging

日志文件位于 logs 目录下,日志的格式在 conf/server.xml 中定义

1
2
3
4
5
6
[root@c72 tomcat]$vim conf/server.xml
...
164 <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
165 prefix="localhost_access_log" suffix=".txt"
166 pattern="%h %l %u %t &quot;%r&quot; %s %b" />
...

组件

每一个组件都一个对象,这些组件大体可分为以下几个类型:

  • 顶级组件:Server,代表整个 Tomcat 容器;

  • 服务类组件:Service,可以创建多个 Service,但一般也只创建一个,用来组织 Engine 和 Connector 的对应关系,一个 service 中只有一个 Engine;

  • 连接器组件:Connector,负责客户端的 HTTP、HTTPS、AJP 等协议连接。一个 Connector 只属于某一个 Engine;

    AJP(Apache Jserv protocol)是一种基于 TCP 的二进制通讯协议

  • 容器类组件:Engine、Host、Context 都是容器类组件;

    • Engine:引擎,真正的处理请求的入口,其内部定义多个虚拟主机 Host。一个 Engine 上可以绑定多个 Connector
    • Host:虚拟主机
    • Context:应用的上下文,配置特定 url 路径映射和目录的映射关系:url => directory
  • 内嵌类组件:valve、logger、realm、loader、manager 等。可以内嵌到其他组件内,在不同容器组件内分别定义。

  • 集群类组件:listener、cluster

多个组件关系 /conf/server.xml 示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version='1.0' encoding='utf-8'?>
<Server port="8015" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<Listener SSLEngine="on" className="org.apache.catalina.core.AprLifecycleListener" />
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
<GlobalNamingResources>
<Resource auth="Container" description="User database that can be updated and saved" factory="org.apache.catalina.users.MemoryUserDatabaseFactory" name="UserDatabase" pathname="conf/tomcat-users.xml" type="org.apache.catalina.UserDatabase" />
</GlobalNamingResources>
<Service name="Catalina">
<Connector connectionTimeout="20000" port="6804" protocol="HTTP/1.1" redirectPort="8443" />
<Engine defaultHost="localhost" name="Catalina">
<Realm className="org.apache.catalina.realm.LockOutRealm">
<Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase" />
</Realm>
<Host appBase="webapps" autoDeploy="true" name="localhost" unpackWARs="true">
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" pattern="%h %l %u %t &quot;%r&quot; %s %b" prefix="localhost_access_log" suffix=".txt" />
</Host>
</Engine>
</Service>
</Server>

tomcat 处理请求过程

假设来自客户的请求为:http://localhost:8080/test/index.jsp

  1. 浏览器端的请求被发送到服务端端口 8080,Tomcat 进程监听在此端口上。通过侦听的 HTTP/1.1 Connector 获得此请求。
  2. Connector 把该请求交给它所在的 Service 的 Engine 来处理,并等待 Engine 的响应
  3. Engine 获得请求 localhost:8080/test/index.jsp,遍历它所有虚拟主机 Host
  4. Engine 匹配到名为 localhost 的 Host。如果匹配不到,就把请求交给该 Engine 中的 defaultHost 处理
  5. localhost Host 获得请求/test/index.jsp,匹配它所拥有的所有 Context
  6. Host 匹配到路径为/test 的 Context
  7. path=/test 的 Context 获得请求 index.jsp,在它的 mapping table 中寻找对应的 servlet
  8. Context 匹配到 URL PATTERN 为 *.jsp 的 servlet,对应于 JspServlet 类构造 HttpServletRequest 对象和 HttpServletResponse 对象,作为参数调用 JspServlet 的 doGet 或 doPost 方法。
  9. Context 把执行完了之后的 HttpServletResponse 对象返回给 Host
  10. Host 把 HttpServletResponse 对象返回给 Engine
  11. Engine 把 HttpServletResponse 对象返回给 Connector
  12. Connector 把 HttpServletResponse 对象返回给浏览器端

应用部署

默认网站根目录是 webapps,其下的每个目录都是一个 WebApp

1
2
3
4
5
6
7
8
9
[root@c72 tomcat]$pwd
/usr/local/tomcat
[root@c72 tomcat]$ll webapps/ # 默认网站根目录
total 24
drwxr-x--- 2 tomcat tomcat 4096 Jan 30 22:50 docs
drwxr-x--- 7 tomcat tomcat 4096 Jan 30 22:50 examples
drwxr-x--- 6 tomcat tomcat 4096 Jan 30 17:19 host-manager
drwxr-x--- 6 tomcat tomcat 4096 Jan 30 17:19 manager
drwxr-x--- 3 tomcat tomcat 4096 Jan 30 22:52 ROOT # 网站默认根目录

JSP WebApp 目录结构

目录结构一般由开发用工具自动生成

1
2
3
4
5
index.html # 主页配置:默认按以下顺序查找主页文件 index.html,index.htm、index.jsp
WEB-INF\ # 当前目录WebApp的私有资源路径,通常存储当前应用使用的web.xml和context.xml配置文件
META-INF\ # 类似于WEB-INF,也是私有资源的配置信息,和WEB-INF/目录一样浏览器无法访问
classes\ # 类文件,当前webapp需要的类
lib\ # 当前应用依赖的jar包

主页设置

  • 全局配置:修改 conf/web.xml 中的 <welcome-file-list> 标签,配置修改后,需要重启 tomcat

    1
    # 略...
  • WebApp 的专用配置文件:以 ROOT 为例,将上面主配置文件 conf/web.xml 中的 <welcome-file-list> 标签,复制到 webapps/ROOT/WEB-INF/web.xml 中,配置修改后,无需重启 tomcat 服务

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    [root@c72 tomcat]$vim webapps/ROOT/WEB-INF/web.xml
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1" metadata-complete="true">
    <display-name>Welcome to Tomcat</display-name>
    <description>Welcome to Tomcat</description>
    <welcome-file-list>
    <welcome-file>index.html</welcome-file> <!-- 调整三个文件顺序 -->
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
    </web-app>
    [root@c72 tomcat]$chown -R tomcat:tomcat ./webapps/ROOT/* # 不要忘记修改属主

应用部署实现

WebApp 应用的归档格式.war.jar

  • .war:WebApp 打包,类 zip 格式文件,通常包括一个应用的所有资源,比如 jsp、html、配置文件等
  • .jar:EJB 类文件的打包压缩类 zip 格式文件,包括很多的 class 文件,网景公司发明

部署方式:自动部署 和 手动部署

  • 自动部署:Tomcat 一旦发现多了一个 web 应用 APP.war 包,默认会自动把它解压缩,加载并启动起来

    1
    2
    # conf/server.xml中文件配置
    <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
  • 手动部署:冷部署 和 热部署

    • 冷部署:将 webapp 放到指定目录,才去启动 Tomcat 服务
    • 热部署:Tomcat 服务不停止,需要依赖 manager、ant 脚本、tcd(tomcat client deployer)等工具
  • 反部署 undeploy:停止 webapp 运行,并从 JVM 上清除已经加载的类,从 Tomcat 应用目录中移除部署的文件

  • 启动 start:是 webapp 能够访问

  • 停止 stop:webapp 不能访问,不能提供服务,但是 JVM 并不清除它

部署基于 JAVA 的博客系统 JPress
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@c72 webapps]$pwd
/usr/local/tomcat/webapps
[root@c72 webapps]$mv /usr/local/src/jpress-v3.3.0.war ./
[root@c72 webapps]$ln -s jpress-v3.3.0 jpress # 创建软连接,方便以后升级
[root@c72 webapps]$ll
total 70024
drwxr-x--- 2 tomcat tomcat 4096 Jan 30 22:50 docs
drwxr-x--- 7 tomcat tomcat 4096 Jan 30 22:50 examples
drwxr-x--- 6 tomcat tomcat 4096 Jan 30 17:19 host-manager
lrwxrwxrwx 1 root root 13 Jan 31 11:35 jpress -> jpress-v3.3.0
drwxr-x--- 6 tomcat tomcat 4096 Jan 31 11:35 jpress-v3.3.0
-rw-r--r-- 1 root root 71677863 Jan 31 11:31 jpress-v3.3.0.war
drwxr-x--- 6 tomcat tomcat 4096 Jan 30 17:19 manager
drwxr-x--- 6 tomcat tomcat 4096 Jan 30 23:01 ROOT
基于 WEB 的管理 Server status 和 Manager APP 实现应用部署

默认的管理页面被禁用,启用方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
# server.xml中Resource的pathname参数指定了授权用户信息的文件:conf/tomcat-users.xml

[root@c72 conf]$vim tomcat-users.xml
#加下面两行,指定用户和密码
<role rolename="manager-gui"/>
<user username="admin" password="123456" roles="manager-gui"/>

[root@c72 manager]$pwd
/usr/local/tomcat/webapps/manage
[root@c72 manager]$vim META-INF/context.xml
# 当前访问地址是10.0.0.72,可以修改正则表达式为
allow="127\.\d+\.\d+\.\d+|::1|0:0:0:0:0:0:0:1|10\.0\.0\.\d+"

基于 WEB 应用程序管理器实现 APP 的部署

访问 /manager 即可

常见配置详解

端口 8005/tcp 安全配置管理
1
2
$ss -ntlp | grep 8005
LISTEN 0 1 [::ffff:127.0.0.1]:8005 [::]:* users:(("java",pid=3117,fd=66))

tomcat 默认监听 8005 端口,这是一个后门,可通过这个端口关闭 tomcat 服务

1
2
$nc 127.0.0.1 8005  # 连接到本机的8005端口
SHUTDOWN # 输入SHUTDOWN,关闭tomcat
1
2
# server.xml
<Server port="8005" shutdown="SHUTDOWN"> # 配置后门的端口,和关闭服务的指令

此行不能被注释,否则无法启动 tomcat 服务

此管理功能建议禁用:

  • 将 SHUTDOWN 改为一串猜不出的字符串实现
  • port 修改成 0, 会使用随机端口,如:36913
  • port 设为-1 等无效端口,将关闭此功能
显示指定的 http 服务器版本信息

默认不显示 tomcat 的 http 的 Server 头信息, 可以指定 tomcat 的 http 的 Server 头信息

示例:冒充 nginx 服务器

1
2
3
4
# 给Connector标签添加Server参数
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" Server="nginx" />
# 重启tomcat
systemctl restart tomcat

其他配置

server.xml 中可以配置 service、connector、Engine、Host 等

1
2
3
4
5
6
7
8
# 一般情况下,一个Server实例配置一个Service,name属性相当于该Service的ID
<Service name="Catalina">
# redirectPort:如果访问HTTPS协议,自动转向这个连接器。但大多数时候,Tomcat并不会开启HTTPS,因为Tomcat往往部署在内部,HTTPS性能较差
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
# 引擎配置
<Engine name="Catalina" defaultHost="localhost">
# defaultHost配置,默认localhost。
<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
多虚拟主机配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# name:相当于 nginx 的 server_name
# appBase:网站根目录,可以使用相对路径或绝对路径
# unpackWARs:是否自动解压 war 格式
# autoDeploy:热部署,自动加载并运行应用
<Host name="test.ljk.cn" appBase="/data/wwwroot/test.ljk.cn" unpackWARs="true" autoDeploy="true">
# className
# directory:日志目录,可以使用相对路径或绝对路径
# 日志文件:prefix.2021-01-31.suffix
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="/data/wwwlogs" prefix="test.ljk.cn_access_log" suffix=".txt" pattern="%h %l %u %t &quot;%r&quot; %s %b" />
</Host>

# 注意日志的权限问题
chown -R tomcat:tomcat /data/wwwlogs

[root@c72 test.ljk.cn]$pwd
/data/wwwroot/test.ljk.cn
[root@c72 test.ljk.cn]$cat ./ROOT/index.html # 注意目录结构
hello test.ljk.cn

基于 web 方式的 Host Manager 虚拟主机管理

略…

Context 配置

Context 类似 Nginx 中的 location

1
2
3
4
5
6
7
8
9
# path
# docBase:相对、绝对路径均可
# reloadable:WEB-INF/classes或META-INF/lib目录下.class文件有改动,自动重载,生产建议关闭
<Context path="/test" docBase="/data/test" reloadable="true" />

# 相当于 Nginx 中:
location /test {
alias /data/test;
}

还可以单独添加日志配置:

1
2
3
<Context path="/test" docBase="/data/test" reloadable="true">
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="localhost_test_log" suffix=".txt" pattern="%h %l %u %t &quot;%r&quot; %s %b" />
</Context>

Valve 组件

valve(阀门)组件可以定义日志

1
2
3
4
5
6
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="localhost_access_log" suffix=".txt" pattern="%h %l %u %t &quot;%r&quot; %s %b" />

# className
# 定义访问日志 org.apache.catalina.valves.AccessLogValve
# 定义访问控制 org.apache.catalina.valves.RemoteAddrValve
<Valve className="org.apache.catalina.valves.RemoteAddrValve" deny="10\.0\.0\.\d+"/>

反向代理和负载均衡

tomcat 的网络并发性能比较差,所以通常只用 tomcat 解析动态页面

两种常见的部署方式:

nginx 监听 80 端口,tomcat 监听 8080 端口,nginx 负责将请求转发给 tomcat 处理

反向代理:

1
2
3
4
5
6
7
8
9
server {
listen 80;
server_name to2b.cn;
access_log off;

location / {
proxy_pass http://to2b.cn:8080; # 反向代理
}
}

负载均衡:

略…

Tomcat Session Replication Cluster

Tomcat 官方实现了 Session 的复制集群,将每个 Tomcat 的 Session 进行相互的复制同步,从而保证所有 Tomcat 都有相同的 Session 信息

这种同步 session 的方式缺点很明显,就是占用内存,所以不适用于 tomcat 服务器太多的情况,如果只有两三台,可以使用

session 共享服务器 msm

msm:memcached session manager,将 Tomcat 的 session 保持到 memcached 或 redis,实现高可用

github 网站链接: https://github.com/magro/memcached-session-manager

安装

参考链接:https://github.com/magro/memcached-session-manager/wiki/SetupAndConfiguration

  1. 下载各种包

    注意:直接点击会报错 501,这时因为 maven 仓库废弃了 http 访问,手动更改为 https 即可

  2. 将相关的 jar 文件都放到 Tomcat 的 lib 目录中即可

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    # Tomcat的Session管理类
    memcached-session-manager-2.3.2.jar
    memcached-session-manager-tc8-2.3.2.jar

    # 驱动类,memcached(spymemcached.jar)、Redis(jedis.jar)
    spymemcached-2.12.3.jar

    # Session数据的序列化、反序列化类,官方推荐 kryo
    msm-kryo-serializer-2.3.2.jar
    kryo-serializers-0.45.jar
    kryo-3.0.3.jar
    minlog-1.3.1.jar
    reflectasm-1.11.9.jar
    asm-5.2.jar
    objenesis-2.6.jar
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    [root@c72 lib]$pwd
    /usr/local/tomcat/lib
    [root@c72 lib]$wget https://repo1.maven.org/maven2/de/javakaffee/msm/memcached-session-manager/2.3.2/memcached-session-manager-2.3.2.jar
    [root@c72 lib]$wget https://repo1.maven.org/maven2/de/javakaffee/msm/memcached-session-manager-tc8/2.3.2/memcached-session-manager-tc8-2.3.2.jar
    [root@c72 lib]$wget https://repo1.maven.org/maven2/net/spy/spymemcached/2.12.3/spymemcached-2.12.3.jar
    [root@c72 lib]$wget https://repo1.maven.org/maven2/de/javakaffee/msm/msm-kryo-serializer/2.3.2/msm-kryo-serializer-2.3.2.jar
    [root@c72 lib]$wget https://repo1.maven.org/maven2/de/javakaffee/kryo-serializers/0.45/kryo-serializers-0.45.jar
    [root@c72 lib]$wget https://repo1.maven.org/maven2/com/esotericsoftware/kryo/3.0.3/kryo-3.0.3.jar
    [root@c72 lib]$wget https://repo1.maven.org/maven2/org/ow2/asm/asm/5.2/asm-5.2.jar
    [root@c72 lib]$wget https://repo1.maven.org/maven2/org/objenesis/objenesis/2.6/objenesis-2.6.jar
    [root@c72 lib]$wget https://repo1.maven.org/maven2/com/esotericsoftware/reflectasm/1.11.9/reflectasm-1.11.9.jar
    [root@c72 lib]$wget https://repo1.maven.org/maven2/com/esotericsoftware/minlog/1.3.1/minlog-1.3.1.jar

sticky 模式

前端 tomcat 和后端 memcached**有关联(粘性)**关系

略…

non-sticky 模式

前端 tomcat 和后端 memcached**无关联(无粘性)**关系,msm 1.4.0 之后版本开始支持

Tomcat session 为中转 Session,对每一个 SessionID 随机选中后端的 memcached 节点 n1(或者 n2)为主 session,而另一个 memcached 节点 n2(或者是 n1)为备 session。产生的新的 Session 会发送给主、备 memcached,并清除本地 Session

如果 n1 下线,n2 则转正。n1 再次上线,n2 依然是主 Session 存储节点

实战案例: memcached 实现 non-sticky 模式

ip 服务 安装软件
10.0.0.71 tomcat1、memcached jdk8、tomcat8、memcached
10.0.0.72 tomcat1、memcached jdk8、tomcat8、memcached
10.0.0.73 负载均衡调度器 haproxy 或者 nginx

负载均衡调度器的安装配置过程:略…

10.0.0.71 和 10.0.0.72 做以下配置:

  1. 安装配置 memcached

    1
    2
    3
    4
    5
    6
    7
    $yum install memcached
    $vim /etc/sysconfig/memcached
    PORT="11211"
    USER="memcached"
    MAXCONN="1024"
    CACHESIZE="64"
    OPTIONS=""
  2. 配置 tomcat 的 context.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <Context>
    ...
    <Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
    memcachedNodes="n1:10.0.0.72:11211,n2:10.0.0.73:11211"
    sticky="false"
    sessionBackupAsync="false"
    lockingMode="uriPattern:/path1|/path2"
    requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$"
    transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory" />
    </Context>

实战案例: redis 实现 non-sticky 模式

参考:https://github.com/magro/memcached-session-manager/wiki/SetupAndConfiguration#example-for-non-sticky-sessions--kryo--redis

Session 问题方案总结

  1. session 绑定,基于 IP 或 session cookie 的。其部署简单,尤其基于 session 黏性的方式,粒度小,对负载均衡影响小。但一旦后端服务器有故障,其上的 session 丢失
  2. session 复制集群,基于 tomcat 实现多个服务器内共享同步所有 session。此方法可以保证任意一台后端服务器故障,其余各服务器上还都存有全部 session,对业务无影响。但是它基于多播实现心跳,TCP 单播实现复制,当设备节点过多,这种复制机制不是很好的解决方案。且并发连接多的时候,单机上的所有 session 占据的内存空间非常巨大,甚至耗尽内存
  3. session 服务器,将所有的 session 存储到一个共享的内存空间中,使用多个冗余节点保存 session,这样做到 session 存储服务器的高可用,且占据业务服务器内存较小。是一种比较好的解决 session 持久的解决方案

以上的方法都有其适用性。生产环境中,应根据实际需要合理选择

Tomcat 性能优化

tomcat 是运行在 jvm 虚拟机上的一个程序,所以 tomcat 的性能和 jvm 密切相关,所以要想优化 tomcat 的性能,除了优化 tomcat 本身的设置,还需要优化 jvm

JVM 组成

1
2
3
4
$java -version
java version "1.8.0_271"
Java(TM) SE Runtime Environment (build 1.8.0_271-b09)
Java HotSpot(TM) 64-Bit Server VM (build 25.271-b09, mixed mode)

JVM:Java Virtual Machine,java 虚拟机

HotSpot 实现了 Java 虚拟机规范,java 虚拟机有好几种,HotSpot 是最主流的一种

  • 类加载子系统:将 java 代码编译成字节码文件,将所需所有类加载到内存,必要时将类实例化成实例
  • 运行时数据区:最消耗内存的空间,需要优化
  • 执行引擎:包括 JIT (JustInTimeCompiler)即时编译器,GC (Garbage Collector)垃圾回收器
  • 本地方法接口:通过 JNI(Java Native Interface)调用 C、C++ 库其他语言,扩展 Java 功能

运行时数据区的heap(堆)是我们关注的重点,这部分可以通过修改设置进行优化,其他部分没有优化的途径

  • MetaSpace:元空间,线程共享,存放已加载的类信息(构造方法、接口定义)、常量(final)、静态变量(static)、运行时常量池等。但实例变量存放在堆内存中。**JDK8 之前叫 Method Area(方法区) **
  • heap:堆,线程共享,虚拟机启动时创建,存放创建的所有对象信息。如果对象无法申请到可用内存将抛出 OOM 异常。堆是靠 GC 垃圾回收器管理的,通过 -Xmx -Xms 指定最大堆和最小堆空间大小
  • Java stack:java 栈,线程私有,每个线程会分配一个栈,存放 java 中 8 大基本数据类型、对象引用、实例的本地变量、方法参数和返回值等,基于 FILO()(First In Last Out),每个方法为一个栈帧
  • Program Counter Register:PC 寄存器,线程私有,就是一个指针,指向方法区中的方法字节码,每一个线程用于记录当前线程正在执行的字节码指令地址。由执行引擎读取下一条指令。因为线程需要切换,当一个线程被切换回来需要执行的时候,需要知道执行到哪里
  • Native Method stack:本地方法栈,线程私有,为本地方法执行构建的内存空间,存放本地方法执行时的局部变量、操作数等
    所谓本地方法,即使用 native 关健字修饰的方法,比如 Thread.sleep 方法。简单的说是非 Java 实现的方法,例如操作系统的 C 编写的库提供的本地方法,Java 调用这些本地方法接口执行。但是要注意,本地方法应该避免直接编程使用,因为 Java 可能跨平台使用,如果用了 Windows API,换到了 Linux 平台部署就有了问题

GC (Garbage Collection) 垃圾收集器

垃圾回收算法

  • 标记-清除 Mark-Sweep
  • 标记-压缩 Mark-Compact
  • 复制 Copying

分代堆内存 GC 策略

JVM 内存空间划分为 heap 区和 非 heap 区

heap 内存空间:

  • Eden Space:伊甸园区
  • Survivor Space:幸存者区,分为两部分,一个是 from 区,一个是 to 区。大小相等、地位相同、可互换
    • from:指的是本次复制数据的源区
    • to:指的是本次复制数据的目标区
  • Tenured Gen:养老区 或 老年代

规律:一般情况 99%的对象都是临时对象

默认空间大小比例:

非 heap 区内存划分:

  • Code Cache
  • Compressed Class Space
  • Metaspace

Minor GC

通常场景下,大多数对象都不会存活很久,而且创建活动非常多,伊甸园区和幸存者区的垃圾回收就非常频繁,所以采用 复制 Copying 算法

  1. 所有新建对象(特大对象直接进入养老区)都出生在 eden
  2. 当 eden 满了,启动 GC。这个称为 Young GC 或者 Minor GC,标记 eden 存活对象,将存活对象复制到幸存者者区(两个幸存者区之一),GC 完成
  3. 当 eden 满了,再次启动 GC,标记 eden 和幸存者区的存活对象,将存活对象复制到另一个幸存者区,GC 完成

以后就重复上面的步骤,每当 eden 满了,就将其中的存货对象和幸存者区(from 区)的存货对象复制到另一个幸存者区(to 区)

但是,如果一个对象一直存活,它最后就在 from、to 来回复制,如果 from 区中对象复制次数达到阈值(默认 15 次,CMS 为 6 次,可通过 java 的选项 -XX:MaxTenuringThreshold=N 指定),就直接复制到养老区

Major GC

进入养老区的数据较少,所以养老区被占满的速度较慢,所以垃圾回收也不频繁,所以较常采用 标记-压缩 Mark-Compact 算法

如果养老区也满了,会触养老区 GC,称为 Old GC 或者 Major GC

因为 Major GC 会对整个 heap 内存空间进行垃圾回收,影响所有的区域,所以 Major GC 又叫 FullGC

java 内存调整相关参数

Java 命令行参考文档: https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html

选项分类:

1
2
3
-选项名称  # 标准选项,所有HotSpot都支持
-X选项名称 # 稳定的非标准选项
-XX:选项名称 # 非标准的不稳定选项,下一个版本可能会取消

常用选项:

1
2
3
4
5
6
7
8
-Xms    # 设置堆内存初始大小,示例:-Xms2g
-Xmx # 设置堆内存上限,示例:-Xms4g
-XX:NewSize # 设置伊甸园区和幸存者区初始大小,示例:-XX:NewSize=128m
-XX:MaxNewSize # 设置伊甸园区和幸存者区上限,示例:-XX:MaxNewSize=256m
-Xmnsize # 同时设置-XX:NewSize 和 -XX:MaxNewSize,示例:-Xmn1g
-XX:NewRatio # 以比例方式设置新生代和老年代,示例:-XX:NewRatio=2,即new/old=1/2
-XX:SurvivorRatio # 以比例方式设置eden和survivor,示例:-XX:SurvivorRatio=6,即eden/survivor=6/1、new/survivor=8/1
-Xss # 设置每个线程私有的栈空间大小,依据具体线程大小和数量,示例:-Xss256k

注意:Xms 和 Xmx 通常设置为相同的值,防止内存碎片化

垃圾收集方式

回收线程 数:GC 线程串行(serial)还是并行(parallel)

  • 串行垃圾回收器:一个 GC 线程完成回收工作
  • 并行垃圾回收器:多个 GC 线程同时一起完成回收工作,充分利用 CPU 资源

工作模式 不同:GC 线程是否和工作线程一起运行

  • 独占垃圾回收器:只有 GC 在工作,STW 一直进行到回收完毕,工作线程才能继续执行

  • 并发垃圾回收器:GC 线程垃圾回收某些阶段可以和工作线程一起进行,如标记阶段并行,回收阶段仍然串行

调整策略

对 JVM 调整策略应用极广

  • 在 WEB 领域中 Tomcat 等

  • 在大数据领域 Hadoop 生态各组件

  • 在消息中间件领域的 Kafka 等

  • 在搜索引擎领域的 ElasticSearch、Solr 等

注意:在不同领域和场景对 JVM 需要不同的调整策略

  • 减少 STW 时长,串行变并行
  • 减少 GC 次数,要分配合适的内存大小

一般情况下,我们大概可以使用以下原则:

  • 客户端或较小程序,内存使用量不大,可以使用串行回收
  • 对于服务端大型计算,可以使用并行回收
  • 大型 WEB 应用,用户端不愿意等待,尽量少的 STW,可以使用并发回收

垃圾回收器

常用垃圾回收器

ParNew:parallel new,并行处理新生代

按分代设置不同垃圾回收器:

新生代:

  • 新生代串行收集器 Serial
  • 新生代并行回收收集器 PS(Parallel Scavenge)
  • 新生代并行收集器 ParNew

老年代:

  • 老年代串行收集器 Serial Old
  • 老年代并行回收收集器 Parallel Old
  • CMS(Concurrent Mark Sweep 并发标记清除算法)收集器

以下收集器不再按明确的分代单独设置:

  • G1(Garbage First)收集器
    • 最新垃圾回收器,从 JDK1.6 实验性提供,JDK1.7 发布,其设计目标是在多处理器、大内存服务器端提供优于 CMS 收集器的吞吐量和停顿控制的回收器。JDK9 将 G1 设为默认的收集器,建议 JDK9 版本以后使用
    • 基于 标记压缩 算法,不会产生大量的空间碎片,有利于程序的长期执行
    • 分为 4 个阶段:初始标记、并发标记、最终标记、筛选回收。并发标记并发执行,其它阶段 STW 只有 GC 线程并行执行
    • G1 收集器是面向服务端的收集器,它的思想就是首先回收尽可能多的垃圾(这也是 Garbage-First 名字的由来)
    • G1 能充分的利用多 CPU,多核环境下的硬件优势,使用多个 CPU 来缩短 STW 停顿的时间 (10ms 以内
    • 可预测的停顿:这是 G1 相对于 CMS 的另一大优势,G1 和 CMS 一样都是关注于降低停顿时间,但是 G1 能够让使用者明确的指定在一个 M 毫秒的时间片段内,消耗在垃圾收集的时间不得超过 N 毫秒
    • 通过此选项指定: +UseG1GC
  • ZGC 收集器:减少 SWT 时长(1ms 以内),可以媲美 C++ 的效率,目前实验阶段
  • Shenandoah 收集器:和 ZGC 竞争关系,目前实验阶段
  • Epsilon 收集器:调试 JDK 使用,内部使用,不用于生产环境
垃圾收集器设置

优化调整 Java 相关参数的目标:尽量减少 FullGC 和 STW

通过以下选项可以单独指定新生代、老年代的垃圾收集器

  • -server
    指定为 Server 模式,也是默认值,一般使用此工作模式
  • -XX:+UseSerialGC
    运行在 Client 模式下,新生代是 Serial, 老年代使用 SerialOld
  • -XX:+UseParNewGC
    新生代使用 ParNew,老年代使用 SerialOld
  • -XX:+UseParallelGC
    运行于 server 模式下,新生代使用 Serial Scavenge, 老年代使用 SerialOld
  • -XX:+UseParallelOldGC
    新生代使用 Paralell Scavenge, 老年代使用 Paralell Old
    -XX:ParallelGCThreads=N 在关注吞吐量的场景使用此选项增加并行线程数
  • -XX:+UseConcMarkSweepGC
    新生代使用 ParNew, 老年代优先使用 CMS,备选方式为 Serial Old
    响应时间要短,停顿短使用这个垃圾收集器
    -XX:CMSInitiatingOccupancyFraction=N 达到老年代的大小的百分比多少时触发回收,默认 68
    -XX:+UseCMSCompactAtFullCollection 开启此值,在 CMS 收集后,进行内存碎片整理
    -XX:CMSFullGCsBeforeCompaction=N 设定多少次 CMS 后,进行一次内存碎片整理
    -XX:+CMSParallelRemarkEnabled 降低标记停顿

JAVA 参数总结

参数名称 含义 默认值 备注
-Xms 初始堆大小 物理内存的 1/64(<1GB) 默认(MinHeapFreeRatio 参数可以调整)空余堆内存小于 40%时,JVM 就会增大堆直到-Xmx 的最大限制.
-Xmx 最大堆大小 物理内存的 1/4(<1GB) 默认(MaxHeapFreeRatio 参数可以调整)空余堆内存大于 70%时,JVM 会减少堆直到-Xms 的最小限制
-Xmn 年轻代大小(1.4or lator) 注意:此处的大小是(eden+ 2 survivor space).与 jmap -heap 中显示的 New gen 是不同的。 整个堆大小=年轻代大小 + 年老代大小 + 持久代大小. 增大年轻代后,将会减小年老代大小.此值对系统性能影响较大,Sun 官方推荐配置为整个堆的 3/8
-XX:NewSize 设置年轻代大小(for 1.3/1.4)
-XX:MaxNewSize 年轻代最大值(for 1.3/1.4)
-XX:PermSize 设置持久代(perm gen)初始值 物理内存的 1/64
-XX:MaxPermSize 设置持久代最大值 物理内存的 1/4
-Xss -Xss JDK5.0 以后每个线程堆栈大小为 1M,以前每个线程堆栈大小为 256K.更具应用的线程所需内存大小进行 调整.在相同物理内存下,减小这个值能生成更多的线程.但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在 3000~5000 左右 一般小的应用, 如果栈不是很深, 应该是 128k 够用的 大的应用建议使用 256k。这个选项对性能影响比较大,需要严格的测试。
-XX:ThreadStackSize Thread StackSize (0 means use defaultstack size) [Sparc: 512;Solaris x86: 320 (was 256prior in 5.0 and earlier);Sparc 64 bit: 1024; Linuxamd64: 1024 (was 0 in 5.0and earlier); all others 0.]
-XX:NewRatio 年轻代(包括 Eden 和两个 Survivor 区)与年老代的比值(除去持久代) -XX:NewRatio=4 表示年轻代与年老代所占比值为 1:4,年轻代占整个堆栈的 1/5Xms=Xmx 并且设置了 Xmn 的情况下,该参数不需要进行设置。
-XX:SurvivorRatio Eden 区与 Survivor 区的大小比值 设置为 8,则两个 Survivor 区与一个 Eden 区的比值为 2:8,一个 Survivor 区占整个年轻代的 1/10
-XX:LargePageSizeInBytes 内存页的大小不可设置过大, 会影响 Perm 的大小 =128m
-XX:+UseFastAccessorMethods 原始类型的快速优化
-XX:+DisableExplicitGC 关闭 System.gc() 这个参数需要严格的测试
-XX:MaxTenuringThreshold 垃圾最大年龄 如果设置为 0 的话,则年轻代对象不经过 Survivor 区,直接进入年老代. 对于年老代比较多的应用,可以提高效率.如果将此值设置为一个较大值,则年轻代对象会在 Survivor 区进行多次复制,这样可以增加对象再年轻代的存活 时间,增加在年轻代即被回收的概率 该参数只有在串行 GC 时才有效
-XX:+AggressiveOpts 加快编译
-XX:+UseBiasedLocking 锁机制的性能改善
-Xnoclassgc 禁用垃圾回收
-XX:SoftRefLRUPolicyMSPerMB 每兆堆空闲空间中 SoftReference 的存活时间 可达的对象在上次被引用后将保留一段时间。 缺省值是堆中每个空闲兆字节的生命周期的一秒钟
-XX:PretenureSizeThreshold 对象超过多大是直接在旧生代分配 0 单位字节 新生代采用 Parallel Scavenge GC 时无效 另一种直接在旧生代分配的情况是大的数组对象,且数组中无外部引用对象
-XX:TLABWasteTargetPercent TLAB 占 eden 区的百分比 1%
-XX:+CollectGen0First FullGC 时是否先 YGC false

并行收集器相关参数:

参数名称 含义 默认值 备注
-XX:+UseParallelGC Full GC 采用 parallelMSC 选择垃圾收集器为并行收集器.此配置仅对年轻代有效.即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集
-XX:+UseParNewGC 设置年轻代为并行收集 可与 CMS 收集同时使用 JDK5.0 以上,JVM 会根据系统配置自行设置,所以无需再设置此值
-XX:ParallelGCThreads 并行收集器的线程数 此值最好配置与处理器数目相等 同样适用于 CMS
-XX:+UseParallelOldGC 年老代垃圾收集方式为并行收集(ParallelCompacting) 这个是 JAVA 6 出现的参数选项
-XX:MaxGCPauseMillis 每次年轻代垃圾回收的最长时间(最大暂停时间) 如果无法满足此时间,JVM 会自动调整年轻代大小,以满足此值
-XX:+UseAdaptiveSizePolicy 自动选择年轻代区大小和相应的 Survivor 区比例 设置此选项后,并行收集器会自动选择年轻代区大小和相应的 Survivor 区比例,以达到目标系统规定的最低相应时间或者收集频率等,此值建议使用并行收集器时,一直打开
-XX:GCTimeRatio 设置垃圾回收时间占程序运行时间的百分比 公式为 1/(1+n)
-XX:+ScavengeBeforeFullGC Full GC 前调用 YGC true

CMS 相关参数:

参数名称 含义 默认值 备注
-XX:+UseConcMarkSweepGC 使用 CMS 内存收集 测试中配置这个以后,-XX:NewRatio=4 的配置可能失效,所以,此时年轻代大小最好用-Xmn 设置
-XX:+AggressiveHeap 试图是使用大量的物理内存 长时间大内存使用的优化,能检查计算资源(内存, 处理器数量) 至少需要 256MB 内存 大量的 CPU/内存, (在 1.4.1 在 4CPU 的机器上已经显示有提升)
-XX:CMSFullGCsBeforeCompaction 多少次后进行内存压缩 由于并发收集器不对内存空间进行压缩,整理,所以运行一段时间以后会产生”碎片”,使得运行效率降低.此值设置运行多少次 GC 以后对内存空间进行压缩,整理
-XX:+CMSParallelRemarkEnabled 降低标记停顿
-XX+UseCMSCompactAtFullCollection 在 FULLGC 的时候,对年老代的压缩 CMS 是不会移动内存的, 因此, 这个非常容易产生碎片,导致内存不够用, 因此, 内存的压缩这个时候就会被启用。增加这个参数是个好习惯。 可能会影响性能,但是可以消除碎片
-XX:+UseCMSInitiatingOccupancyOnly 使用手动定义初始化定义开始 CMS 收集 禁止 hostspot 自行触发 CMSGC
-XX:CMSInitiatingOccupancyFraction=70 使用 cms 作为垃圾回收使用 70%后开始 CMS 收集 92
-XX:+UseConcMarkSweepGC 使用 CMS 内存收集 测试中配置这个以后,-XX:NewRatio=4 的配置可能失效,所以,此时年轻代大小最好用-Xmn 设置
-XX:CMSInitiatingPermOccupancyFraction 设置 PermGen 使用到达多少比率时触发 92
-XX:+CMSIncrementalMode 设置为增量模式 用于单 CPU 情况
-XX:+CMSClassUnloadingEnabled

辅助信息:

参数名称 含义 默认值 备注
-XX:+PrintGC 输出形式:[GC 118250K->113543K(130112K),0.0094143 secs] [Full GC121376K->10414K(130112K),0.0650971 secs]
-XX:+PrintGCDetails 输出形式:[GC [DefNew:8614K->781K(9088K),0.0123035 secs] 118250K->113543K(130112K),0.0124633 secs] [GC[DefNew: 8614K->8614K(9088K), 0.0000665secs] 121376K->10414K(130112K),0.0436268 secs]
-XX:+PrintGCTimeStamps
-XX:+PrintGC:PrintGCTimeStamps 可与-XX:+PrintGC -XX:+PrintGCDetails 混合使用输出形式:11.851: [GC98328K->93620K(130112K),0.0082960 secs]
-XX:+PrintGCApplicationStoppedTime 打印垃圾回收期间程序暂停的时间.可与上面混合使用 输出形式:Total time forwhich application threadswere stopped: 0.0468229seconds
-XX:+PrintGCApplicationConcurrentTime 打印每次垃圾回收前,程序未中断的执行时间.可与上面混合使用 输出形式:Application time:0.5291524 seconds
-XX:+PrintHeapAtGC 打印 GC 前后的详细堆栈信息
-Xloggc:filename 把相关日志信息记录到文件以便分析. 与上面几个配合使用
-XX:+PrintGC 输出形式:[GC 118250K->113543K(130112K),0.0094143 secs] [Full GC121376K->10414K(130112K),0.0650971 secs]
-XX:+PrintClassHistogram garbagecollectsbeforeprintingthehistogram
-XX:+PrintTLAB 查看 TLAB 空间的使用情况
-XX:+PrintTenuringDistribution 查看每次 minor GC 后新的存活周期的阈值

JVM 相关工具

$JAVA_HOME/bin 下

命令 说明
jps 查看所有 jvm 进程
jinfo 查看进程的运行环境参数,主要是 jvm 命令行参数
jstat 对 jvm 应用程序的资源和性能进行实时监控
jstack 查看所有线程的运行状态,程序员常用堆栈情况查看工具
jmap 查看 jvm 占用物理内存的状态
jhat +UseParNew
jconsole 图形工具
jvisualvm 图形工具

JMX

JMX(Java Management Extensions,即 Java 管理扩展)是一个为应用程序、设备、系统等植入管理功能的框架。JMX 可以跨越一系列异构操作系统平台、系统体系结构和网络传输协议,灵活的开发无缝集成的系统、网络和服务管理应用。
JMX 最常见的场景是监控 Java 程序的基本信息和运行情况,任何 Java 程序都可以开启 JMX,然后使用 JConsole 或 Visual VM 进行预览。

为 Java 程序开启 JMX 很简单,只要在运行 Java 程序的命令后面指定如下命令即可

在 tomcat 开启远程 JMX 如下配置:

1
2
3
4
5
-Dcom.sun.management.jmxremote #启用远程监控JMX
-Djava.rmi.server.hostname=10.0.0.71 #tomcat主机自己的IP地址,不要写zabbix服务器的地址
-Dcom.sun.management.jmxremote.port=1100 #默认启动的JMX端口号,要和zabbix添加主机时候的端口一致
-Dcom.sun.management.jmxremote.ssl=false #不启用ssl认证
-Dcom.sun.management.jmxremote.authenticate=false #不启用用户名密码认证

jps

JVM 进程状态工具

jinfo

输出给定的 java 进程的所有配置信息

jstat

输出指定的 java 进程的统计信息

jstack

查看指定的 java 进程的线程栈的相关信息,程序员常用堆栈情况查看工具

jmap

Memory Map,用于查看堆内存的使用状态

jhat

Java Heap Analysis Tool 堆分析工具

jconsole

查看 Java 进程信息的图形化工具

  1. 配置 JMX,在服务器 10.0.0.71 上做如下配置

    1
    2
    3
    4
    5
    6
    $vim /usr/local/tomcat/bin/catalina.sh
    # OS specific support. $var _must_ be set to either true or false.
    # 添加以下配置,注意位置,如果添加在文件末尾,则无法生效
    JAVA_OPTS="-Djava.rmi.server.hostname=10.0.0.71 -Dcom.sun.management.jmxremote.port=1100 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false"

    $systemctl restart tomcat.service
  2. 在 windows 上配置好 jdk,去 jdk/bin 目录下,启动 jconsole.exe

jvisualvm

监控 jvm 的图形化工具,可以本机监控,也可以远程监控,下面以远程监控为例:

  1. 配置 JMX,在服务器 10.0.0.71 上做如下配置

    1
    2
    3
    4
    5
    6
    $vim /usr/local/tomcat/bin/catalina.sh
    # OS specific support. $var _must_ be set to either true or false.
    # 添加以下配置,注意位置,如果添加在文件末尾,则无法生效
    JAVA_OPTS="-Djava.rmi.server.hostname=10.0.0.71 -Dcom.sun.management.jmxremote.port=1100 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false"

    $systemctl restart tomcat.service
  2. 在 windows 上配置好 jdk,去 jdk/bin 目录下,启动 jvisualvm.exe,添加远程主机 -> 选择添加 jmx 连接

使用 jvisualvm 的 Visual GC 插件

安装完成后,重启即可,不过因为是远程连接,还需要做其他配置,这个问题百度解决,或者选择使用本机监控

Jprofiler

JProfiler 是一款功能强大的 Java 开发分析工具,它可以快速的帮助用户分析出存在的错误,软件还可对需要的显示类进行标记,包括了内存的分配情况和信息的视图等

这个工具是收费的,可以用来定位 OOM 的问题原因

JProfiler 官网:http://www.ej-technologies.com/products/jprofiler/overview.html

Tomcat 性能优化常用配置

内存空间优化

1
2
3
4
5
6
7
JAVA_OPTS="-server -Xms4g -Xmx4g -XX:NewSize= -XX:MaxNewSize= "

-server:服务器模式,VM运行在server模式,为在服务器端最大化程序运行速度而优化
-Xms:堆内存初始化大小
-Xmx:堆内存空间上限
-XX:NewSize=:新生代空间初始化大小
-XX:MaxNewSize=:新生代空间最大值

生产案例:

1
2
3
4
5
6
7
8
[root@centos8 ~]#vim /usr/local/tomcat/bin/catalina.sh
JAVA_OPTS="-server -Xms4g -Xmx4g -Xss512k -Xmn1g -
XX:CMSInitiatingOccupancyFraction=65 -XX:+AggressiveOpts -XX:+UseBiasedLocking -
XX:+DisableExplicitGC -XX:MaxTenuringThreshold=10 -XX:NewRatio=2 -
XX:PermSize=128m -XX:MaxPermSize=512m -XX:CMSFullGCsBeforeCompaction=5 -
XX:+ExplicitGCInvokesConcurrent -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -
XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -
XX:LargePageSizeInBytes=128m -XX:+UseFastAccessorMethods"

一台 tomcat 服务器并发连接数不高,生产建议分配物理内存通常 4G 到 8G 较多,如果需要更多连接,一般会利用虚拟化技术实现多台 tomcat

线程池调整

1
2
3
4
5
[root@centos8 ~]#vim /usr/local/tomcat/conf/server.xml
......
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000"
redirectPort="8443" />
......

常用属性:

1
2
3
4
5
6
7
8
9
10
connectionTimeout  # 连接超时时长,单位ms
maxThreads # 最大线程数,默认200
minSpareThreads # 最小空闲线程数
maxSpareThreads # 最大空闲线程数
acceptCount # 当启动线程满了之后,等待队列的最大长度,默认100
URIEncoding # URI 地址编码格式,建议使用 UTF-8
enableLookups # 是否启用客户端主机名的DNS反向解析,缺省禁用,建议禁用,就使用客户端IP就行
compression # 是否启用传输压缩机制,建议 "on",CPU和流量的平衡
compressionMinSize # 启用压缩传输的数据流最小值,单位是字节
compressableMimeType # 定义启用压缩功能的 MIME 类型text/html、text/css、text/javascript

语法:

  • 扩展名通常为 yml 或 yaml
  • 在单一文件第一行,用连续三个连字号”-“ 开始,还有选择性的连续三个点号( … )用来表示文件的结尾
  • 次行开始正常写 Playbook 的内容,一般建议写明该 Playbook 的功能
  • 使用#号注释代码
  • 缩进必须是统一的,不能空格和 tab 混用
  • 缩进的级别也必须是一致的,同样的缩进代表同样的级别,程序判别配置的级别是通过缩进结合换行来实现的
  • YAML 文件内容是区别大小写的,key/value 的值均需大小写敏感
  • 多个 key/value 可同行写也可换行写,同行使用,分隔
  • key 后面冒号要加一个空格 比如: key: value
  • value 可是个字符串,也可是另一个列表

支持的数据类型

  • 标量:单个的、不可再分的值
  • 对象:键值对的集合,又称为映射(mapping)/ 哈希(hashes) / 字典(dictionary)
  • 数组:一组按次序排列的值,又称为序列(sequence) / 列表(list)

scalar 标量

标量是最基本的,不可再分的值,包括:字符串、布尔值、整数、浮点数、Null、时间、日期

1
2
3
4
5
# 一行
name: lujinkai
# 使用缩进
name:
wang

dictionary 字典

字典由多个键值对组成,键值用: 分割,注意:后面有一个空格;值可以是标量、列表或其他字典

1
2
3
4
5
6
# 一行
account: { name: lujinkai, age: 26 }
# 使用缩进
account:
name: lujinkai
age: 26

list 列表

列表由多个元素组成,元素可以是标量、字典或其他列表

1
2
3
4
5
6
7
# 一行
skills: [ linux, golang, python ]
# 使用缩进
skills:
- linux
- golang
- python

范例:使用 yaml 表示一个家庭

1
2
3
4
5
6
7
8
9
10
11
12
13
name: John Smith
age: 41
gender: Male
spouse:
name: Jane Smith
age: 37
gender: Female
children:
- name: Jimmy Smith
age: 17
gender: Male
- { name: Jenny Smith, age: 13, gender: Female }
- { name: hao Smith, age: 20, gender: Male }

cat

如果没有指定文件,或者指定文件为 “-”,则从标准输入读取

1
2
3
4
5
-n    # 对输出内容标行号
-b # 对输出内容非空行标行号
-E # 显示行结束符$
-A # 显示所有控制符
-s # 压缩连续的空行成一行

nl

相当于 cat -b

tac

cat 倒着写 tac,功能也是恰如其名:逆向显示文本内容

tr

translate,转换、删除、去重标准输入中的字符,将结果写入到标准输出

1
2
3
4
5
6
7
tr [OPTION]... SET1 [SET2]

-d # 删除
-c # 取补集
-s # 对set1进行去重操作
-t # 将第一个字符集对应字符转化为第二字符集对应的字符
-c # 取字符集的补集

tr 类似于 sed 命令,但是比 sed 简单,所有 tr 能实现的功能,sed 都能实现

示例:

1
2
3
4
5
6
7
8
9
10
# 将a换成b
$tr a b
# 生成8位随机数
$ cat /dev/urandom | tr -dc A-Za-z0-9 | head -c8
1imvyPoH
# 将a.log中大写输出为小写
$ cat a.log
HELLO WORD
$ tr A-z a-z <a.log
hello word

-

重定向有时会使用 - 符号,重定向到标准输出

rev

将每行的内容逆向显示

1
2
-c   #指定前几个字符
-n #指定前几行,当n是负数(-#),表示舍弃最后#行

tail

1
2
3
-f   #将文件尾部内容显示在终端,并不断刷新,常用于观察实时日志
-c #指定后几个字符
-n #指定后几行,当n是显示整数(+#),表示从第#行开始到结束

less 和 more

分页查看文件内容

一般搭配管道符使用,less 和 more 差不多,区别在于 more 只能向下翻页

hexdump、od、xxd

查看非文本内容

最常的是 hexdump,常用选项 -C

cut

提取文本文件或 STDIN 数据的指定列

1
2
3
4
5
6
7
8
9
10
cut [OPTION]... [FILE]...

-d delimiter #指明分隔符
-f fileds
n #第n个字段, 例如: 3
n,n[,n] #离散的多个字段, 例如: 1,3,6
n-n #连续的多个字段, 例如: 1-6
混合使用: 1-3,7
-c #按字符切割
--output-delimiter #指定输出分隔符
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
[19:22:09 root@centos7 data]#cut -d : -f1-3 --output-delimiter=. passwd
root.x.0
bin.x.1
daemon.x.2
adm.x.3
lp.x.4
sync.x.5
shutdown.x.6
halt.x.7
mail.x.8
operator.x.11
games.x.12
ftp.x.14
nobody.x.99
systemd-network.x.192
dbus.x.81
polkitd.x.999
sshd.x.74
postfix.x.89
lujinkai.x.1000
test.x.1001
test2.x.1002
gentoo.x.1003
tomcat.x.1004
mysql.x.1005
[14:49:02 root@centos7 data]#cut -c1-3 passwd
roo
bin
dae
adm
lp:
syn
shu
hal
mai
ope
gam
ftp
nob
sys
dbu
pol
ssh
pos
luj
tes
tes
gen
tom
mys

paste

合并多个文件,合并多个文件同行号的列到一行

1
2
3
4
paste [OPTION]... [FILE]...

-d #分隔符, 指定分隔符, 默认分隔符TAB
-s #所有行合成一行显示

wc

文本统计,用于统计文件或 STDIN 的行总数、单词总数、字节总数和字符总数

1
2
3
4
-l   #统计行数
-w #统计单词数 (规则是有空格或者换行分隔才是单词)
-m #打印字符数 (空格和换行和tab都是字符)
-L #统计文件中最长行的长度

sort

字典排序, 从小到大

1
2
3
4
5
6
7
8
-r  #反方向
-R #随机排序
-n #自然排序
-h #人类可读排序, 如: 2k 1G
-f #忽略大小写
-u #合并重复项, 即去重
-t #指定字段界定符
-k #配合-t使用, 指定使用第几个字符来排序

示例:按照 UID 进行排序

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
[21:18:38 root@centos7 data]#cat /etc/passwd | sort -n -t: -k 3
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
games:x:12:100:games:/usr/games:/sbin/nologin
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
dbus:x:81:81:System message bus:/:/sbin/nologin
postfix:x:89:89::/var/spool/postfix:/sbin/nologin
nobody:x:99:99:Nobody:/:/sbin/nologin
systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin
polkitd:x:999:998:User for polkitd:/:/sbin/nologin
lujinkai:x:1000:1000:lujinkai:/home/lujinkai:/bin/bash
test:x:1001:1001::/home/test:/bin/bash
test2:x:1002:1002::/home/test2:/bin/bash
gentoo:x:1003:1003:Gentoo Distribution:/home/gentoo:/bin/csh
tomcat:x:1004:1006::/home/tomcat:/sbin/nologin
mysql:x:1005:1009::/home/mysql:/bin/bash

uniq

去重,从输入中删除前后相接的重复的行

1
2
3
4
5
uniq [OPTION]... [FILE]...

-c # 显示每行重复出现的次数, 重复的行必须相邻, 否则会单独统计
-d # 仅显示重复的行
-u # 仅显示不重复的行

uniq 常和 sort 命令一起配合使用

1
sort userlist.txt | uniq -c

diff 和 path

比较文本

diff

diff 命令比较两个文件之间的区别

常用选项 -u

patch

复制在其他文件中进行的改变 (要谨慎使用)

1
2
diff -u foo.conf foo2.conf > foo.patch
patch -b foo.conf foo.patch

cmp

比较二进制文件,查看二进制文件的不同

练习

1、找出 ifconfig “网卡名” 命令结果中本机的 IPv4 地址

1
2
3
4
5
6
7
8
9
10
11
[root@47105171233 wwwlogs]# cut -d' ' -f1 lujinkai.cn_nginx.log | sort -t' ' -k1 | uniq -c | sort -nr | head -n10
276 112.64.64.36
186 183.136.190.62
184 112.64.64.47
184 112.64.64.40
184 112.64.64.33
121 176.9.32.203
94 47.92.207.41
92 112.64.64.43
92 112.64.64.39
92 112.64.64.35

2、查出分区空间使用率的最大百分比值
3、查出用户 UID 最大值的用户名、UID 及 shell 类型
4、查出/tmp 的权限,以数字方式显示
5、统计当前连接本机的每个远程主机 IP 的连接数,并按从大到小排序

官方解释称,Docker 作为一个完整的容器技术堆栈,在其创建之初就不是为了将其嵌入 Kubernetes 而设计的。除了其作为容器运行时本身的作用以外,Docker 还包含了一系列方便用户交互的 UX 更改,而这些额外的功能对于 Kubernetes 来说过于臃肿。

事实上,Docker 并不符合 Kubernetes 的容器运行时接口标准(CRI),官方必须要维护一个名为 Dockershim 的中间件才能够把 Docker 当作 Kubernetes 的容器运行时来使用。因此,官方建议用户使用符合 CRI 的 containerd 或 CRI-O 作为取代 Docker 的容器运行时,并表示最早将于 v1.23 版本把 Dockershim 从 Kubelet 中移除。

不过,Kubernetes 官方表示用户今后依然可以使用 Docker 来构建容器镜像,而 Docker 生成的镜像实际上也是一个 OCI(Open Container Initiative)镜像。无论使用什么工具来构建镜像,任何符合 OCI 标准的镜像在 Kubernetes 看来都是一样的。containerd 和 CRI-O 则可以提取这些镜像并运行它们。

https://github.com/easzlab/kubeasz

https://github.com/easzlab/kubeasz/blob/master/docs/setup/00-planning_and_overall_intro.md

角色 ip 主机名 备注
master1 10.0.1.1/21 k8s-master1.ljk.cn 同时部署 ansible
master2 10.0.1.2/21 k8s-master2.ljk.cn
node1 10.0.1.3/21 k8s-node1.ljk.cn
node2 10.0.1.4/21 k8s-node2.ljk.cn
etcd1 10.0.2.1/21 k8s-etcd1.ljk.cn
etcd2 10.0.2.2/21 k8s-etcd2.ljk.cn
etcd3 10.0.2.3/21 k8s-etcd3.ljk.cn
HAProxy1 + keepalived
给 master 做负载均衡
10.0.2.1/21
vip:10.0.2.188:8443
和 etcd1 混用
HAProxy2 + keepalived
给 master 做负载均衡
10.0.2.2/2
vip:10.0.2.188:8443
和 etcd2 混用
harbor 10.0.2.3/21 和 etcd3 混用

架构图:

准备

校准时区和同步时间

  • 所有服务器,校准时区

    1
    $ timedatectl set-timezone Asia/Shanghai
  • 确保各节点时区设置一致、时间同步。 如果你的环境没有提供 NTP 时间同步,推荐集成安装 chrony

    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
    [root@k8s-master1 ~]$apt install -y chrony
    [root@k8s-master1 ~]$sed -n '/^#/!p' /etc/chrony/chrony.conf
    server ntp.aliyun.com iburst
    server time1.cloud.tencent.com iburst
    keyfile /etc/chrony/chrony.keys
    driftfile /var/lib/chrony/chrony.drift
    logdir /var/log/chrony
    maxupdateskew 100.0
    rtcsync
    makestep 1 3
    allow 10.0.0.0/16
    allow 10.0.1.0/16
    allow 10.0.2.0/16
    allow 10.0.3.0/16
    allow 10.0.4.0/16
    allow 10.0.5.0/16
    allow 10.0.6.0/16
    allow 10.0.7.0/16
    local stratum 10
    [root@k8s-master1 ~]$systemctl restart chronyd.service

    # 其他节点
    $apt install -y chrony
    $vim /etc/chrony/chrony.conf
    ...
    server 10.0.1.1 iburst # 只修改此行
    ...
    $systemctl restart chronyd.service

python

kubeasz 基于 ansible,而 ansible 基于 python 的,所以所有服务器都需要安装 python 环境

1
apt update && apt install python3 –y

ansible

在 master1 上安装 ansible

1
2
3
4
5
6
7
8
9
10
[root@k8s-master1 ~]$apt-get install git python3-pip -y
# 优化pip
[root@k8s-master1 ~]$vim ~/.pip/pip.conf # 没有就创建
[global]
index-url = https://mirrors.aliyun.com/pypi/simple/

[install]
trusted-host=mirrors.aliyun.com

[root@k8s-master1 ~]$pip3 install ansible # 这一步时间很长,建议更改为阿里的pip源

设置 ssh 免密

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
function ssh_push_key() {
ips=(
10.0.1.2
10.0.1.3
10.0.1.4
10.0.2.1
10.0.2.2
10.0.2.3
)
[ "$1" ] && ips=($@)
apt install -y sshpass
[ -f ~/.ssh/id_rsa ] || ssh-keygen -t rsa -f ~/.ssh/id_rsa -P '' >/dev/null 2>&1
export SSHPASS=ljkk
for ip in ${ips[@]}; do
(
timeout 5 ssh $ip echo "$ip: SSH has passwordless access!"
if [ $? != 0 ]; then
sshpass -e ssh-copy-id -o StrictHostKeyChecking=no root@$ip >/dev/null 2>&1
timeout 5 ssh $ip echo "$ip: SSH has passwordless access!" || echo "$ip: SSH has no passwordless access!"
fi
) &
done
wait
}
ssh_push_key

keepalived

10.0.2.1/21 和 10.0.2.2/21 均安装 keepalived

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
# 安装
$apt install keepalived -y
$vim /etc/keepalived/keepalived.conf
global_defs {
router_id keepalived1
vrrp_skip_check_adv_addr
vrrp_garp_interval 0
vrrp_gna_interval 0
vrrp_mcast_group4 224.0.0.18
}

vrrp_instance VI_1 {
state MASTER
interface eth0
virtual_router_id 1 # 另一个keepalived设置为2
priority 100 # 另一个keepalived设置为120
advert_int 1
authentication {
auth_type PASS
auth_pass 123456
}
virtual_ipaddress {
10.0.2.188 dev eth0 label eth0:0
}
}
$systemctl restart keepalived.service

haproxy

10.0.2.1/21 和 10.0.2.2/21 均安装 haproxy

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
# 编译安装lua,过程略...
# 编译安装haproxy
$wget http://www.haproxy.org/download/2.2/src/haproxy-2.2.6.tar.gz
$apt install make gcc build-essential libssl-dev zlib1g-dev libpcre3 libpcre3-dev libsystemd-dev libreadline-dev -y
$useradd -r -s /sbin/nologin -d /var/lib/haproxy haproxy
$mkdir /usr/local/haproxy
$tar zxvf haproxy-2.2.6.tar.gz
$cd haproxy-2.2.6/
$make ARCH=x86_64 TARGET=linux-glibc USE_PCRE=1 USE_OPENSSL=1 USE_ZLIB=1 USE_SYSTEMD=1 USE_LUA=1 LUA_INC=/usr/local/lua/src/ LUA_LIB=/usr/local/lua/src/
$make install PREFIX=/usr/local/haproxy
$vim /etc/profile # export PATH=/usr/local/haproxy/sbin:$PATH
$. /etc/profile
$haproxy -v
$haproxy -V
$haproxy -vv

# 配置文件
$mkdir /var/lib/haproxy
$mkdir /etc/haproxy
$vim /etc/haproxy/haproxy.cfg
global
chroot /usr/local/haproxy
stats socket /var/lib/haproxy/haproxy.sock mode 600 level admin
user haproxy
group haproxy
daemon
nbproc 1
maxconn 100000
pidfile /var/lib/haproxy/haproxy.pid

defaults
option redispatch
option abortonclose
option http-keep-alive
option forwardfor
maxconn 100000
mode http
timeout connect 120s
timeout server 120s
timeout client 120s
timeout check 5s

listen stats
stats enable
mode http
bind 0.0.0.0:9999
log global
stats uri /haproxy-status
stats auth admin:123456

listen k8s-api-8443
bind 10.0.2.188:8443 # 注意,要和hosts的[ex-lb]对应起来
mode tcp
server master1 10.0.1.1:6443 check inter 2000 fall 3 rise 5 # 6443端口是默认的,不能改
server master2 10.0.1.2:6443 check inter 2000 fall 3 rise 5

# 启动文件
$mkdir /etc/haproxy/conf.d/
$vim /lib/systemd/system/haproxy.service
[Unit]
Description=HAProxy Load Balancer
After=syslog.target network.target

[Service]
ExecStartPre=/usr/local/haproxy/sbin/haproxy -f /etc/haproxy/haproxy.cfg -f /etc/haproxy/conf.d/ -c -q
ExecStart=/usr/local/haproxy/sbin/haproxy -Ws -f /etc/haproxy/haproxy.cfg -f /etc/haproxy/conf.d/ -p /var/lib/haproxy/haproxy.pid
ExecReload=/bin/kill -USR2 $MAINPID

[Install]
WantedBy=multi-user.target

$systemctl start haproxy.service

下载项目源码

1
2
3
4
5
$export release=2.2.4
$curl -C- -fLO --retry 3 https://github.com/easzlab/kubeasz/releases/download/${release}/easzup
$chmod +x ./easzup
# 使用工具脚本下载
$./easzup -D

配置集群参数

1
2
cd /etc/ansible
cp example/hosts.multi-node hosts
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
# 'etcd' cluster should have odd member(s) (1,3,5,...)
# variable 'NODE_NAME' is the distinct name of a member in 'etcd' cluster
[etcd]
10.0.2.1 NODE_NAME=etcd1
10.0.2.2 NODE_NAME=etcd2
10.0.2.3 NODE_NAME=etcd3

# master node(s)
[kube-master]
10.0.1.1
10.0.1.2

# work node(s)
[kube-node]
10.0.1.3
#10.0.1.4

# [optional] harbor server, a private docker registry
# 'NEW_INSTALL': 'yes' to install a harbor server; 'no' to integrate with existed one
# 'SELF_SIGNED_CERT': 'no' you need put files of certificates named harbor.pem and harbor-key.pem in directory 'down'
[harbor]
#192.168.1.8 HARBOR_DOMAIN="harbor.yourdomain.com" NEW_INSTALL=no SELF_SIGNED_CERT=yes

# [optional] loadbalance for accessing k8s from outside
[ex-lb]
10.0.2.1 LB_ROLE=backup EX_APISERVER_VIP=10.0.2.188 EX_APISERVER_PORT=8443
10.0.2.2 LB_ROLE=master EX_APISERVER_VIP=10.0.2.188 EX_APISERVER_PORT=8443

# [optional] ntp server for the cluster
[chrony]
#192.168.1.1

[all:vars]
# --------- Main Variables ---------------
# Cluster container-runtime supported: docker, containerd
CONTAINER_RUNTIME="docker"

# Network plugins supported: calico, flannel, kube-router, cilium, kube-ovn
CLUSTER_NETWORK="calico"

# Service proxy mode of kube-proxy: 'iptables' or 'ipvs'
PROXY_MODE="ipvs"

# K8S Service CIDR, not overlap with node(host) networking
SERVICE_CIDR="192.168.0.0/16"

# Cluster CIDR (Pod CIDR), not overlap with node(host) networking
CLUSTER_CIDR="10.10.0.0/16"

# NodePort Range
NODE_PORT_RANGE="30000-60000"

# Cluster DNS Domain
CLUSTER_DNS_DOMAIN="ljk.local."

# -------- Additional Variables (don't change the default value right now) ---
# Binaries Directory
bin_dir="/usr/local/bin"

# CA and other components cert/key Directory
ca_dir="/etc/kubernetes/ssl"

# Deploy Directory (kubeasz workspace)
base_dir="/etc/ansible"

验证配置是否成功:

1
$ansible all -m ping

安装

配置 harbor

10.0.2.3,详细步骤参考:docker 仓库管理

因为笔记本性能问题,harbor 和 etcd 复用一个节点,所以 harbor 的 hostname 配置为 ip,如果 harbor 单独使用一台服务器,应该配置为 hostname

00-规划集群和配置介绍

https://github.com/easzlab/kubeasz/blob/master/docs/setup/00-planning_and_overall_intro.md

01-创建证书和安装准备

https://github.com/easzlab/kubeasz/blob/master/docs/setup/01-CA_and_prerequisite.md

1
2
3
[root@k8s-master1 ansible]$pwd
/etc/ansible
[root@k8s-master1 ansible]$ansible-playbook ./01.prepare.yml

02-安装 etcd 集群

https://github.com/easzlab/kubeasz/blob/master/docs/setup/02-install_etcd.md

1
[root@k8s-master1 ansible]$ansible-playbook ./02.etcd.yml

03-安装 docker 服务

https://github.com/easzlab/kubeasz/blob/master/docs/setup/03-install_docker.md

1
[root@k8s-master1 ansible]$ansible-playbook 03.docker.yml

04-安装 master 节点

https://github.com/easzlab/kubeasz/blob/master/docs/setup/04-install_kube_master.md

1
[root@k8s-master1 ansible]$ansible-playbook 04.kube-master.yml
1
2
3
4
5
6
7
8
9
# 报错:目标主机没有/opt/kube目录
TASK [kube-master : 配置admin用户rbac权限] ****************************************************************************************
fatal: [10.0.1.2]: FAILED! => {"changed": false, "checksum": "95ce2ef4052ea55b6326ce0fe9f6da49319874c7", "msg": "Destination directory /opt/kube does not exist"}
changed: [10.0.1.1]

# 解决:去目标主机手动创建/opt/kube目录
[root@k8s-master2 ~]$mkdir /opt/kube
# 重新执行ansible-playbook
[root@k8s-master1 ansible]$ansible-playbook 04.kube-master.yml

05-安装 node 节点

https://github.com/easzlab/kubeasz/blob/master/docs/setup/05-install_kube_node.md

1
[root@k8s-master1 ansible]$ansible-playbook 05.kube-node.yml

06-安装集群网络

https://github.com/easzlab/kubeasz/blob/master/docs/setup/06-install_network_plugin.md

公有云推荐使用 flannel,自建 IDC 推荐使用 calico,如果选择 calico,一定要关闭 IPIP

1
2
3
[root@k8s-master1 ansible]$vim roles/calico/defaults/main.yml
CALICO_IPV4POOL_IPIP: "off" # 将always修改为off,关闭IPIP
[root@k8s-master1 ansible]$ansible-playbook 06.network.yml

07-安装集群插件

https://github.com/easzlab/kubeasz/blob/master/docs/setup/07-install_cluster_addon.md

1

安装 dashboard

安装部署:

1
2
3
4
5
6
7
8
9
[root@k8s-master1 ansible]$cd manifests/dashboard/
[root@k8s-master1 dashboard]$pwd
/etc/ansible/manifests/dashboard
# 部署dashboard 主yaml配置文件
[root@k8s-master1 dashboard]$kubectl apply -f ./kubernetes-dashboard.yaml
# 创建可读可写 admin Service Account
[root@k8s-master1 dashboard]$kubectl apply -f ./admin-user-sa-rbac.yaml
# 创建只读 read Service Account
[root@k8s-master1 dashboard]$kubectl apply -f ./read-user-sa-rbac.yaml

验证:

1
2
3
4
5
6
7
8
9
10
11
12
# 查看pod 运行状态
[root@k8s-master1 dashboard]$kubectl get pod -n kube-system | grep dashboard
dashboard-metrics-scraper-79c5968bdc-cqg8b 1/1 Running 0 60m
kubernetes-dashboard-c4c6566d6-crqfc 1/1 Running 0 60m
# 查看dashboard service
[root@k8s-master1 dashboard]$kubectl get service -n kube-system|grep dashboard
dashboard-metrics-scraper ClusterIP 192.168.233.194 <none> 8000/TCP 60m
kubernetes-dashboard NodePort 192.168.249.138 <none> 443:44420/TCP 60m # 44420
# 查看集群服务
$kubectl cluster-info|grep dashboard
# 查看pod 运行日志
$kubectl logs kubernetes-dashboard-c4c6566d6-crqfc -n kube-system

访问:

登录:

  • token:令牌

    1
    2
    3
    4
    5
    # 查看用户 admin-user 的 token
    $kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep admin-user | awk '{print $1}')

    # 查看用户 read-user 的 token
    $kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep read-user | awk '{print $1}')
  • kubeconfig:配置文件

    将 token 追加到 /root/.kube/config 文件中,然后将此文件作为登录的凭证

测试网络

1
2
3
4
5
6
7
8
9
10
11
12
# 安装几个测试pod
# 注意node上的/etc/docker/daemon.json 要配置"insecure-registries": ["10.0.2.3"]
kubectl run net-test1 --image=10.0.2.3/baseimages/alpine:3.13 sleep 360000
kubectl run net-test2 --image=10.0.2.3/baseimages/alpine:3.13 sleep 360000
kubectl run net-test3 --image=10.0.2.3/baseimages/alpine:3.13 sleep 360000
# 查看pod,确保跑在不同的node上,这样才能测试网络
$kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
net-test1 1/1 Running 0 7m11s 10.10.210.4 10.0.1.4 <none> <none>
net-test2 1/1 Running 0 2m27s 10.10.210.5 10.0.1.4 <none> <none>
net-test3 1/1 Running 0 2m6s 10.10.246.66 10.0.1.3 <none> <none>
# 测试:进入pod,ping其他node的pod的ip,确保能ping通

安装 CoreDNS

1
2
3
# 进入到pod中,无法ping通域名,这时因为没有配置dns
$ping kubernetes.default.svc.ljk.local
ping: bad address 'kubernetes.default.svc.ljk.local'

自行安装

coredns 官方 github 提供的安装脚本,依赖 kube-dns,所以我们使用 kubernetes 官方提供的 yaml 进行安装

去 kubernetes 的官方 github 仓库,下载二进制文件

打开后,往下拉,下载 client、node、server 三个源码包,注意要下载 amd64 版本,不要下载错了

1
2
3
4
https://dl.k8s.io/v1.20.2/kubernetes.tar.gz
https://dl.k8s.io/v1.20.2/kubernetes-client-linux-amd64.tar.gz
https://dl.k8s.io/v1.20.2/kubernetes-node-linux-amd64.tar.gz
https://dl.k8s.io/v1.20.2/kubernetes-server-linux-amd64.tar.gz
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
[root@k8s-master1 src]$tar zxf kubernetes.tar.gz
[root@k8s-master1 src]$tar zxf kubernetes-client-linux-amd64.tar.gz
[root@k8s-master1 src]$tar zxf kubernetes-node-linux-amd64.tar.gz
[root@k8s-master1 src]$tar zxf kubernetes-server-linux-amd64.tar.gz
[root@k8s-master1 src]$cd kubernetes/
[root@k8s-master1 kubernetes]$ll
total 33672
drwxr-xr-x 10 root root 4096 Jan 13 21:44 ./
drwxr-xr-x 3 root root 4096 Jan 29 19:38 ../
drwxr-xr-x 2 root root 4096 Jan 13 21:44 addons/
drwxr-xr-x 3 root root 4096 Jan 13 21:40 client/
drwxr-xr-x 9 root root 4096 Jan 13 21:46 cluster/
drwxr-xr-x 2 root root 4096 Jan 13 21:46 docs/
drwxr-xr-x 3 root root 4096 Jan 13 21:46 hack/
-rw-r--r-- 1 root root 34426961 Jan 13 21:44 kubernetes-src.tar.gz
drwxr-xr-x 3 root root 4096 Jan 13 21:44 LICENSES/
drwxr-xr-x 3 root root 4096 Jan 13 21:41 node/
-rw-r--r-- 1 root root 3386 Jan 13 21:46 README.md
drwxr-xr-x 3 root root 4096 Jan 13 21:41 server/
-rw-r--r-- 1 root root 8 Jan 13 21:46 version
[root@k8s-master1 kubernetes]$ll server/bin/ # 这些都是go写的二进制程序,可以直接执行的
...
[root@k8s-master1 kubernetes]$cd ./cluster/addons/dns/coredns/
[root@k8s-master1 coredns]$ll
total 44
drwxr-xr-x 2 root root 4096 Jan 29 21:39 ./
drwxr-xr-x 5 root root 4096 Jan 13 21:46 ../
-rw-r--r-- 1 root root 4957 Jan 13 21:46 coredns.yaml.base
-rw-r--r-- 1 root root 5007 Jan 13 21:46 coredns.yaml.in # 基于coredns.yaml.base
-rw-r--r-- 1 root root 5009 Jan 13 21:46 coredns.yaml.sed # 基于coredns.yaml.base
-rw-r--r-- 1 root root 1075 Jan 13 21:46 Makefile
-rw-r--r-- 1 root root 344 Jan 13 21:46 transforms2salt.sed
-rw-r--r-- 1 root root 287 Jan 13 21:46 transforms2sed.sed
[root@k8s-master1 coredns]$mkdir -p /etc/ansible/manifests/dns/coredns # 规划一下目录
[root@k8s-master1 coredns]$cp coredns.yaml.base /etc/ansible/manifests/dns/coredns/coredns.yaml
[root@k8s-master1 coredns]$cd /etc/ansible/manifests/dns/coredns/
[root@k8s-master1 coredns]$ls
coredns.yaml
[root@k8s-master1 coredns]$vim coredns.yaml
# 修改以下几行
image: 10.0.2.3/baseimages/coredns:1.7.0 # 将镜像换为本地harbor地址
resources:
limits:
memory: 512Mi # 分配内存,生产中要给的大一些
kubernetes ljk.local in-addr.arpa ip6.arpa { # 设置域名 ljk.local
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
clusterIP: 192.168.0.2 # 设置dns服务器,这个可以进入pod查看/etc/resolv.conf的nameserver
[root@k8s-master1 coredns]$kubectl apply -f coredns.yaml
[root@k8s-master1 coredns]$kubectl get pod -n kube-system | grep coredns
coredns-7589f7f68b-jp5kz 1/1 Running 0 39m
1
2
3
4
5
6
7
# 查看 server ip
$kubectl get service -o wide -A
NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
default kubernetes ClusterIP 192.168.0.1 <none> 443/TCP 29h <none>
kube-system dashboard-metrics-scraper ClusterIP 192.168.233.194 <none> 8000/TCP 6h27m k8s-app=dashboard-metrics-scraper
kube-system kube-dns ClusterIP 192.168.0.2 <none> 53/UDP,53/TCP,9153/TCP 47m k8s-app=kube-dns
kube-system kubernetes-dashboard NodePort 192.168.249.138 <none> 443:44420/TCP 6h27m k8s-app=kubernetes-dashboard

通过 dashboard 进入 pod,测试一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 服务名称.命名空间.svc.ljk.local
/ # nslookup kubernetes.default.svc.ljk.local
Server: 192.168.0.2
Address: 192.168.0.2#53

Name: kubernetes.default.svc.ljk.local
Address: 192.168.0.1 # 测试成功

/ # nslookup dashboard-metrics-scraper.kube-system.svc.ljk.local
Server: 192.168.0.2
Address: 192.168.0.2#53

Name: dashboard-metrics-scraper.kube-system.svc.ljk.local
Address: 192.168.233.194

kubeasz 安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@k8s-master ansible]$vim roles/cluster-addon/defaults/main.yml
# 关闭其他组件的自动安装,只安装coredns
metricsserver_install: "no"
dashboard_install: "no"
ingress_install: "no"

[root@k8s-master ansible]$ansible-playbook ./07.cluster-addon.yml
...

# 测试,随便进入一个pod
/ # nslookup kubernetes.default.svc.ljk.local
Server: 192.168.0.2
Address: 192.168.0.2:53

Name: kubernetes.default.svc.ljk.local
Address: 192.168.0.1

关于负载均衡

kubeasz 的逻辑是在每个 node 上安装一个 haproxy,如果 master 的数量超过 1 个,node 和 master 的通信就走 haproxy

1
2
3
4
5
6
[root@k8s-node1 kubernetes]$cat ~/.kube/config | grep server
server: https://127.0.0.1:6443
[root@k8s-node1 kubernetes]$cat /etc/kubernetes/kube-proxy.kubeconfig | grep server
server: https://127.0.0.1:6443
[root@k8s-node1 kubernetes]$cat /etc/kubernetes/kubelet.kubeconfig | grep server
server: https://127.0.0.1:6443

可是这样无法避免 haproxy 的单点失败问题,而且外部访问 master 的时候还得走 vip,所以不如直接只搭建一套 keeplived + haproxy,然后不管 master 还是 node,所有和 apiserver 的通信都走 vip,kubeasz 提供了搭建 keeplived + haproxy 的 playbook,在 01.prepare.yml 中打开注释即可,当然也可以自己搭建

使用

添加节点

非常简单

添加 master

https://github.com/easzlab/kubeasz/blob/master/docs/op/op-master.md

1
easzctl add-master $ip

添加 node

https://github.com/easzlab/kubeasz/blob/master/docs/op/op-node.md

1
easzctl add-node $ip

集群升级

小版本升级

下载最新版本的 kubernetes,全部解压后,在 kubernetes/server/bin 目录下,都是编译好的二进制程序

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
kubernetes-client-linux-amd64.tar.gz
kubernetes-node-linux-amd64.tar.gz
kubernetes-server-linux-amd64.tar.gz
kubernetes.tar.gz

# 将以上四个都解压,得到一个目录:kubernetes
$pwd
/usr/local/src/kubernetes/server/bin
$ll
total 986844
drwxr-xr-x 2 root root 4096 Feb 19 00:26 ./
drwxr-xr-x 3 root root 4096 Feb 19 00:30 ../
-rwxr-xr-x 1 root root 46710784 Feb 19 00:26 apiextensions-apiserver*
-rwxr-xr-x 1 root root 39251968 Feb 19 00:26 kubeadm*
-rwxr-xr-x 1 root root 44699648 Feb 19 00:26 kube-aggregator*
-rwxr-xr-x 1 root root 118218752 Feb 19 00:26 kube-apiserver*
-rw-r--r-- 1 root root 8 Feb 19 00:24 kube-apiserver.docker_tag
-rw------- 1 root root 123035136 Feb 19 00:24 kube-apiserver.tar
-rwxr-xr-x 1 root root 112758784 Feb 19 00:26 kube-controller-manager*
-rw-r--r-- 1 root root 8 Feb 19 00:24 kube-controller-manager.docker_tag
-rw------- 1 root root 117575168 Feb 19 00:24 kube-controller-manager.tar
-rwxr-xr-x 1 root root 40263680 Feb 19 00:26 kubectl*
-rwxr-xr-x 1 root root 114113512 Feb 19 00:26 kubelet*
-rwxr-xr-x 1 root root 39518208 Feb 19 00:26 kube-proxy*
-rw-r--r-- 1 root root 8 Feb 19 00:24 kube-proxy.docker_tag
-rw------- 1 root root 120411648 Feb 19 00:24 kube-proxy.tar
-rwxr-xr-x 1 root root 43745280 Feb 19 00:26 kube-scheduler*
-rw-r--r-- 1 root root 8 Feb 19 00:24 kube-scheduler.docker_tag
-rw------- 1 root root 48561664 Feb 19 00:24 kube-scheduler.tar
-rwxr-xr-x 1 root root 1634304 Feb 19 00:26 mounter*

master

  1. 关闭服务

    1
    2
    3
    4
    5
    6
    $systemctl stop \
    kube-apiserver.service \
    kubelet.service \
    kube-scheduler.service \
    kube-controller-manager.service \
    kube-proxy.service
  2. 拷贝新版本,覆盖旧版本

    1
    scp kube-apiserver kubelet kube-scheduler kube-controller-manager kube-proxy kubectl 10.0.1.31:/usr/local/bin
  3. 启动服务

    1
    2
    3
    4
    5
    6
    $systemctl start \
    kube-apiserver.service \
    kubelet.service \
    kube-scheduler.service \
    kube-controller-manager.service \
    kube-proxy.service

注:因为 master 做了 haproxy+keepalived,所以只要不是全部 master 同时关闭服务就没问题,其实都全部 master 都关闭服务,短时间也没事

node

  1. 关闭服务

    1
    $systemctl stop kubelet.service kube-proxy.service
  2. 拷贝新版本,覆盖旧版本

    1
    scp kubelet kube-proxy kubectl 10.0.1.33:/usr/local/bin
  3. 启动服务

    1
    $systemctl start kubelet.service kube-proxy.service

注:关闭 kubelet 和 kube-proxy 不影响访问已经创建的 Pod

如果 node 数量比较多,写个脚本循环升级即可

大版本升级

kubectl 是 kube-apiserver 的命令行客户端,就像 redis-cli 是 redis 的命令行客户端

安装 kubectl

https://kubernetes.io/zh/docs/tasks/tools/install-kubectl/

https://developer.aliyun.com/mirror/kubernetes?spm=a2c6h.13651102.0.0.3e221b11Qo8aZk

kubectl 详解

https://kubernetes.io/zh/docs/reference/kubectl/overview/

https://kubernetes.io/docs/reference/kubectl/overview/

1
kubectl [command] [TYPE] [NAME] [flags]

command:指定要对一个或多个资源执行的操作,例如 creategetdescribedelete
TYPE:指定资源类型。不区分大小写, 可以指定单数、复数或缩写形式。例如,以下命令输出相同的结果:

1
2
3
kubectl get pod pod1
kubectl get pods pod1
kubectl get po pod1

NAME:指定资源的名称。名称区分大小写。 如果省略名称,则显示所有资源的详细信息

1
2
3
4
5
6
# 类型相同的资源 TYPE1 name1 name2 name3...
kubectl get pod example-pod1 example-pod2
# 多个资源类型 TYPE1/name1 TYPE1/name2 TYPE2/name3 ...
kubectl get pod/example-pod1 replicationcontroller/example-rc1
# 用一个或多个文件指定资源 -f file1 -f file2 -f file3...
kubectl get -f ./pod.yaml

flags:指定可选的参数。例如,可以使用 -s 或 -server 指定 Kubernetes API 服务器的地址和端口

从命令行指定的参数优先级最高

基本命令

1
2
3
4
5
6
7
8
create        # 创建资源
expose # 将资源暴露为新的 Service
run # 在集群上运行一个特定的镜像
set # 使用命令行修改资源
edit # 使用编辑器修改资源,交互式
explain # 资源的详细信息
get # 显示一个或多个资源
delete # 删除资源

get

1
2
3
4
5
6
7
8
9
10
11
12
13
14
kubectl get [(-o|--output=)json|yaml|wide|custom-columns=...|custom-columns-file=...|go-template=...|go-template-file=...|jsonpath=...|jsonpath-file=...] (TYPE[.VERSION [.GROUP] [NAME | -l label] | TYPE[.VERSION][.GROUP]/NAME ...) [flags] [options]

# 示例
kubectl get pods
kubectl get pods -A 相当于 kubectl get pods --all-namespaces
kubectl get pods -o wide
kubectl get replicationcontroller web
kubectl get deployments.v1.apps -o json
kubectl get -o json pod web-pod-13je7
kubectl get -f pod.yaml -o json
kubectl get -k dir/
kubectl get -o template pod/web-pod-13je7 --template={{.status.phase}}
kubectl get rc,services
kubectl get rc/web service/frontend pods/web-pod-13je7

create

1
2
3
4
5
6
kubectl create -f FILENAME [options]

# 示例:
kubectl create -f ./pod.json
cat pod.json | kubectl create -f -
kubectl create -f docker-registry.yaml --edit -o json

expose

1
2
3
4
5
6
7
8
9
10
kubectl expose (-f FILENAME | TYPE NAME) [--port=port] [--protocol=TCP|UDP|SCTP] [--target-port=number-or-name] [--name=name] [--external-ip=external-ip-of-service] [--type=type] [options]

# 示例:
kubectl expose rc nginx --port=80 --target-port=8000
kubectl expose -f nginx-controller.yaml --port=80 --target-port=8000
kubectl expose pod valid-pod --port=444 --name=frontend
kubectl expose service nginx --port=443 --target-port=8443 --name=nginx-https
kubectl expose rc streamer --port=4100 --protocol=UDP --name=video-stream
kubectl expose rs nginx --port=80 --target-port=8000
kubectl expose deployment nginx --port=80 --target-port=8000

set ★★★

1
2
3
4
5
6
7
8
kubectl set SUBCOMMAND [options]

SUBCOMMAND:
env # 更新环境变量,对应deployment的`spec.template.spec.containers.env`
image # 更新镜像
resources Update resource requests/limits on objects with pod templates
selector Set the selector on a resource
serviceaccount Update ServiceAccount of a resource
1
2
3
4
5
6
7
kubectl set image (-f FILENAME | TYPE NAME) CONTAINER_NAME_1=CONTAINER_IMAGE_1 ... CONTAINER_NAME_N=CONTAINER_IMAGE_N [options]

# 示例
kubectl set image deployment/nginx busybox=busybox nginx=nginx:1.9.1
kubectl set image deployments,rc nginx=nginx:1.9.1 --all
kubectl set image daemonset abc *=nginx:1.9.1
kubectl set image -f path/to/file.yaml nginx=nginx:1.9.1 --local -o yaml

edit

1
2
3
4
5
6
7
kubectl edit (RESOURCE/NAME | -f FILENAME) [options]

# 示例:
kubectl edit svc/docker-registry
KUBE_EDITOR="nano" kubectl edit svc/docker-registry
kubectl edit job.v1.batch/myjob -o json
kubectl edit deployment/mydeployment -o yaml --save-config

run

1
2
3
4
5
6
7
kubectl run NAME --image=<image> [--env="key=value"] [--port=port] [--dry-run=server|client] [--overrides=inline-json] [--command] -- [COMMAND] [args...] [options]

# 示例:
kubectl run nginx --image=nginx # 使用默认命令启动
kubectl run nginx --image=nginx -- <arg1> <arg2> ... <argN> #默认命令,自定义参数
kubectl run nginx --image=nginx --command -- <cmd> <arg1> ... <argN> #非默认命令
kubectl run test1 --image=harbor.ljk.local/baseimages/alpine:3.12.4 -- sleep 3600

explain

1
2
kubectl explain RESOURCE [options]
kubectl explain <type>.<fieldName>[.<fieldName>] [options]

使用 kubectl explain 查看配置清单怎么写,例如:使用 kubectl explain deployment 查看创建 deployment 资源的配置清单怎么写,使用 kubectl explain namespace 查看创建 namespace 资源的配置清单怎么写,等等

delete

1
2
3
4
5
6
7
8
9
10
11
kubectl delete ([-f FILENAME] | [-k DIRECTORY] | TYPE [(NAME | -l label | --all)]) [options]

# 示例:
kubectl delete -f ./pod.json
kubectl delete -k dir
cat pod.json | kubectl delete -f -
kubectl delete pod,service baz foo
kubectl delete pods,services -l name=myLabel
kubectl delete pod foo --now
kubectl delete pod foo --force
kubectl delete pods --all

部署命令

参考:回滚 Deployment

1
2
3
rollout       管理资源的升级和回滚
scale 弹性伸缩Pod数量
autoscale 自动设置运行的pod数量(水平自动伸缩)

rollout ★★★

1
2
3
4
5
6
7
8
9
kubectl rollout SUBCOMMAND [options]

SUBCOMMAND:
history 查看指定资源的操作记录
pause 暂停升级
restart Restart a resource
resume 继续升级
status Show the status of the rollout
undo 升级回滚
1
2
3
4
5
6
7
8
9
10
11
12
13
14
kubectl rollout history (TYPE NAME | TYPE/NAME) [flags] [options]

# 示例:
kubectl rollout history deployment/abc
deployments "nginx-deployment"
EVISION CHANGE-CAUSE
1 kubectl apply --filename=nginx-deployment.yaml --record=true
2 kubectl set image deployment/abc nginx=nginx:1.9.1 --record=true
3 kubectl set image deployment/abc nginx=nginx:1.91 --record=true

kubectl rollout history daemonset/abc --revision=3 # 指定版本

kubectl rollout history deployment.v1.apps/nginx-deployment
kubectl rollout history deployment.v1.apps/nginx-deployment --revision=2
1
2
3
4
5
6
kubectl rollout undo (TYPE NAME | TYPE/NAME) [flags] [options]

# 示例:
kubectl rollout undo deployment/abc # 回滚到上个版本
kubectl rollout undo daemonset/abc --to-revision=3 # 回滚到指定版本
kubectl rollout undo --dry-run=server deployment/abc

scale

1
2
3
4
5
6
7
8
kubectl scale [--resource-version=version] [--current-replicas=count] --replicas=COUNT (-f FILENAME | <TYPE> <NAME>) [options]

# 示例:
kubectl scale --replicas=3 rs/foo
kubectl scale --replicas=3 -f foo.yaml
kubectl scale --current-replicas=2 --replicas=3 deployment/mysql
kubectl scale --replicas=5 rc/foo rc/bar rc/baz
kubectl scale --replicas=3 statefulset/web

autoscale

1
2
3
4
5
kubectl autoscale (-f FILENAME | TYPE NAME | TYPE/NAME) [--min=MINPODS] --max=MAXPODS [--cpu-percent=CPU] [options]

# 示例:
kubectl autoscale deployment foo --min=2 --max=10
kubectl autoscale rc foo --max=5 --cpu-percent=80

集群管理命令

1
2
3
4
5
6
7
certificate   Modify certificate resources.
cluster-info 集群信息
top 集群资源 (CPU/Memory/Storage) 使用情况
cordon 警戒线,标记node不被调度,即不参加pod调度
uncordon 取消警戒标记为cordon的node,即参加pod调度
drain 驱逐node上的pod,用于node下线等场景
taint 给node标记污点

cordon

uncordon

故障处理和调试命令

1
2
3
4
5
6
7
8
9
describe      显示资源或资源组的详细信息
logs 打印pod中容器的日志(标准输出的日志)
attach Attach to a running container
exec 和docker的exec实现一样的功能,只是更加智能,不用考虑容器在哪个节点上
port-forward Forward one or more local ports to a pod
proxy Run a proxy to the Kubernetes API server
cp Copy files and directories to and from containers.
auth Inspect authorization
debug Create debugging sessions for troubleshooting workloads and nodes

describe ★★★

1
2
3
4
5
6
7
8
9
kubectl describe (-f FILENAME | TYPE [NAME_PREFIX | -l label] | TYPE/NAME) [options]

# 示例:
kubectl describe nodes kubernetes-node-emt8.c.myproject.internal
kubectl describe pods/nginx
kubectl describe -f pod.json
kubectl describe pods
kubectl describe po -l name=myLabel
kubectl describe pods frontend

经常用此命令查看 pod 的日志

pod 没有启动的原因排错:

  1. kubectl get
  2. kubectl describe
  3. kubectl logs
  4. 到 pod 所在的宿主机去看宿主机的系统日志

logs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
kubectl logs [-f] [-p] (POD | TYPE/NAME) [-c CONTAINER] [options]

# 示例:
kubectl logs nginx
kubectl logs nginx --all-containers=true
kubectl logs -lapp=nginx --all-containers=true
kubectl logs -p -c ruby web-1
kubectl logs -f -c ruby web-1
kubectl logs -f -lapp=nginx --all-containers=true
kubectl logs --tail=20 nginx
kubectl logs --since=1h nginx
kubectl logs --insecure-skip-tls-verify-backend nginx
kubectl logs job/hello
kubectl logs deployment/nginx -c nginx-1

exec

1
2
3
4
5
6
7
8
9
kubectl exec (POD | TYPE/NAME) [-c CONTAINER] [flags] -- COMMAND [args...] [options]

# 示例:
kubectl exec mypod -- date
kubectl exec mypod -c ruby-container -- date
kubectl exec mypod -c ruby-container -i -t -- bash -il
kubectl exec mypod -i -t -- ls -t /usr
kubectl exec deploy/mydeployment -- date
kubectl exec svc/myservice -- date

注意:进入容器只是查看信息,不要修改配置,如果需要修改配置,只能重新打镜像

高级命令

1
2
3
4
5
6
diff          Diff live version against would-be applied version
apply Apply a configuration to a resource by filename or stdin
patch Update field(s) of a resource
replace Replace a resource by filename or stdin
wait Experimental: Wait for a specific condition on one or many resources.
kustomize Build a kustomization target from a directory or a remote url.

diff

apply ★★★

1
2
3
4
5
6
7
8
kubectl apply (-f FILENAME | -k DIRECTORY) [options]

# 示例:
kubectl apply -f ./pod.json
kubectl apply -k dir/
cat pod.json | kubectl apply -f -
kubectl apply --prune -f manifest.yaml -l app=nginx
kubectl apply --prune -f manifest.yaml --all --prune-whitelist=core/v1/ConfigMap

patch

修改、更新资源字段,支持 JSON 和 YAML 格式

1
2
3
4
5
6
7
8
9
10
kubectl patch (-f FILENAME | TYPE NAME) [-p PATCH|--patch-file FILE] [options]

-p:更新json资源文件

# 示例:
kubectl patch node k8s-node-1 -p '{"spec":{"unschedulable":true}}'
kubectl patch node k8s-node-1 -p $'spec:\n unschedulable: true'
kubectl patch -f node.json -p '{"spec":{"unschedulable":true}}'
kubectl patch pod valid-pod -p '{"spec":{"containers":[{"name":"kubernetes-serve-hostname","image":"new image"}]}}'
kubectl patch pod valid-pod --type='json' -p='[{"op": "replace", "path": "/spec/containers/0/image", "value":"newimage"}]'

replace

使用配置文件或 stdin 来替换当前资源

1
2
3
4
5
6
7
kubectl replace -f FILENAME [options]

# 示例:
kubectl replace -f ./pod.json
cat pod.json | kubectl replace -f -
kubectl get pod mypod -o yaml | sed 's/\(image: myimage\):.*$/\1:v4/' | kubectl replace -f -
kubectl replace --force -f ./pod.json

createapplyreplace 的区别:

  • create:指定一个动作,新建资源
  • replace:指定一个动作,替换资源
  • apply:指定目标状态,不关心实现的过程
1
2
3
4
# 新建资源
kubectl create -f nginx.yaml
# 修改nginx.yaml,然后用修改后替换当前的
kubectl replace -f nginx.yaml

相当于:

1
2
3
4
# 新建资源,相当于 `kubectl create`
kubectl apply -f nginx.yaml
# 修改nginx.yaml,然后更新资源,相当于 `kubectl path`
kubectl apply -f nginx.yaml

wait

kustomize

设置命令

1
2
3
label         Update the labels on a resource
annotate Update the annotations on a resource
completion Output shell completion code for the specified shell (bash or zsh)
1
[root@k8s-master ~]$kubectl completion bash > /etc/profile.d/kubectl_completion.sh

其他命令

1
2
3
4
5
6
7
8
api-resources 打印服务器上支持的API资源
api-versions 以 "group/version" 的形式打印API versions
config Modify kubeconfig files
plugin Provides utilities for interacting with plugins.
version Print the client and server version information
alpha
convert
options

输出选项

Formatting output

1
2
3
4
kubectl [command] [TYPE] [NAME] -o <output_format>

-o wide:适合查看
-o json:适合监控

Sorting list objects

1
kubectl [command] [TYPE] [NAME] --sort-by=<jsonpath_exp>

示例:常用操作

示例:创建和使用插件

资源

1
kubectl api-resources

kubeadm 提供了kubeadm initkubeadm join ,可以快速创建 k8s 集群

kubeadm 通过执行必要的操作来启动和运行最小可用集群。按照设计,它只关注启动引导,而非配置机器

我们希望在 kubeadm 之上构建更高级别以及更加合规的工具,理想情况下,使用 kubeadm 作为所有部署工作的基准将会更加易于创建一致性集群

安装各种 “锦上添花” 的扩展,例如 Kubernetes Dashboard、监控方案、以及特定云平台的扩展,都不在讨论范围内

安装 kubeadm

https://developer.aliyun.com/mirror/kubernetes?spm=a2c6h.13651102.0.0.3e221b11Qo8aZk

kubeadm

1
kubeadm [command] [flags]

command:

1
2
3
4
5
6
7
8
9
10
11
alpha           # kubeadm 处于测试阶段的命令,尽量不要用,`kubeadm alpha --help`查看有哪些命令
certs # 处理kubernetes证书
completion # bash 命令补全,需要安装 bash-completion
config # 管理 kubeadm 集群的配置,该配置保留在集群的 ConfigMap 中
help # Help about any command
init # 设置一个Kubernetes控制平面
join # 将节点加入到k8s集群
reset # 还原使用`kubeadm init`或者`kubeadm join`对系统产生的环境变化
token # 管理 token
upgrade # 升级 k8s 版本
version # 查看版本信息

flags:

1
2
3
4
5
6
7
8
9
--add-dir-header # 如果为true,则将文件目录添加到日志消息的头部
-h, --help
--log-file string # 如果非空,则使用此日志文件
--log-file-max-size uint # 定义日志文件可以增长到的最大大小。单位是字节。当该值为0时,表示不限制文件的最大大小。1800(默认)
--one-output # 如果为真,则只将日志写入其本机的严重级别(vs也写入每个较低的严重级别)
--rootfs string # 到“真实的”主机根文件系统的路径。
--skip-headers # 如果为true,则避免在日志消息中使用头前缀
--skip-log-headers # 如果为true,在打开日志文件时避免头文件
-v, --v Level # 日志级别详细程度的编号

kubeadm completion

让 kubeadm 命令支持自动补全,只支持 bash 和 zsh 两种环境。
kubeadm completion bash 打印的脚本加载到环境变量即可:

1
2
3
4
5
6
7
yum install bash-completion -y
touch /etc/profile.d/kubeadm_completion.sh
kubeadm completion bash > /etc/profile.d/kubeadm_completion.sh
exit # 重新登录终端

[root@master01 ~]$kubeadm # tab键就会出现命令提示
alpha certs completion config help init join reset token upgrade version

kubeadm init

此命令初始化一个 Kubernetes 控制平面节点

参考:https://kubernetes.io/zh/docs/reference/setup-tools/kubeadm/kubeadm-init

kubeadm init 执行流程:

  1. 检查系统状态,有的检查只报错,有的检查会退出流程
  2. 生成自签名 CA(可用使用用户提供的现有 CA)为集群中的每个组件设置身份,默认存放在 /etc/kubernetes/kpi
  3. 在 /etc/kubernetes/ 中为 kubelet、controller-manger 和 scheduler 编写配置文件,让它们可以连接 API Server,每个文件都需要有自己的标识。同时还需要为管理用途编写文件 admin.conf
  4. alpha 相关,略…
  5. 为 API Server、controller-manager 和 scheduler 生成静态的 manifest 文件。如果没有提供外部 etcd,也会为 etcd 生成一个额外的 Pod manifest 文件。
    静态 Pod 的 manifest 文件将会写入 /etc/kubernetes/manifest,kubelet 监控这个目录,在启动时判断是否生成 Pod。
    一旦控制平面 Pod 启动并运行,kubeadm init 将会继续运行
  6. alpha 相关,略…
  7. 将标签和 taint 应用到 master 节点,这样就不会有额外的工作负载运行在该节点上
  8. 生成 token 以让额外的 node 能够将它们自己注册到这个 master,默认有效期 24 小时
  9. 创建所有必需的配置让 node 能够通过 Bootstrap Token 和 TLS Bootstrap 机制来加入集群
    1. 编写一个 ConfigMap 来提供加入集群所需的所有信息,并设置相关的 RBAC 访问规则
    2. 让 Bootstrap Token 能够访问 CSR 鉴权 API
    3. 为所有新的 CSR 请求配置 auto-approval
  10. 安装内部的 DNS 服务(默认 CoreDNS),通过 Api Server 安装 kube-proxy 插件组件
    请注意:就算部署了 DNS 服务,在安装 CNI 之前它也不会被调度到 node 上
  11. alpha 相关,略…
1
2
kubeadm init [command]
kubeadm init [flags]

command:

1
phase # 使用此命令调用init工作流的单个阶段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
kubeadm init phase [command]
# command:
addon # Install required addons for passing Conformance tests
bootstrap-token # Generates bootstrap tokens used to join a node to a cluster
certs # Certificate generation
control-plane # 生成建立控制平面所需的所有静态pod清单文件
etcd # Generate static Pod manifest file for local etcd
kubeconfig # 生成建立控制平面所需的所有kubeconfig文件和管理kubeconfig文件
kubelet-finalize # Updates settings relevant to the kubelet after TLS bootstrap
kubelet-start # Write kubelet settings and (re)start the kubelet
mark-control-plane # Mark a node as a control-plane
preflight # Run pre-flight checks
upload-certs # Upload certificates to kubeadm-certs
upload-config # Upload the kubeadm and kubelet configuration to a ConfigMap

# 每个命令都可以使用--help查询帮助,例如:`kubeadm init phase addon --help`

flags:标星的是需要指定的,其他没有标星的一般情况下使用默认值即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
★ --apiserver-advertise-address string # K8S API Server 将要监听的监听的 IP 地址,设置为master的IP,默认自动探测
★ --apiserver-bind-port int32 # API Server 绑定的端口,默认为 6443
--apiserver-cert-extra-sans strings # 可选的证书额外信息,用于指定 API Server 的服务器证书。可以是 IP 地址也可以是 DNS 名称
--cert-dir string # 证书的存储路径,缺省路径为 /etc/kubernetes/pki
--certificate-key string # 定义一个用于加密 kubeadm-certs Secret 中的控制平台证书的密钥
--config string # kubeadm 配置文件的路径
★ --control-plane-endpoint string # 为控制平面指定一个稳定的 IP 地址或 DNS 名称,即配置一个可以长期使用且是高可用的 VIP 或者域名,k8s 多 master 高可用基于此参数实现
--cri-socket string # 要连接的 CRI(容器运行时接口:Container Runtime Interface)套接字的路径。如果为空,则 kubeadm 将尝试自动检测此值;仅当安装了多个 CRI 或具有非标准 CRI 插槽时,才使用此选项
--dry-run # 不要应用任何更改;只是输出将要执行的操作,其实就是测试运行
--experimental-patches string # 用于存储 kustomize 为静态 pod 清单所提供的补丁的路径
--feature-gates string # 一组用来描述各种功能特性的键值(key=value)对。选项是:IPv6DualStack=true|false (ALPHA - default=false)
--ignore-preflight-errors strings # 可以忽略检查过程 中出现的错误信息,比如忽略 swap,如果为 all 就忽略所有
★ --image-repository string # 设置用于拉取控制平面镜像的容器仓库,默认为 k8s.gcr.io
★ --kubernetes-version string # 为控制平面选择一个特定的 Kubernetes 版本,默认为 stable-1
--node-name string # 指定master节点的名称,默认使用hostname
★ --pod-network-cidr string # 设置 pod ip 地址范围, control plane 会自动将 网络发布到其他节点的node,让其上启动的容器使用此网络,flannel插件的默认值是 10.244.0.0/16
★ --service-cidr string # 指定service 的IP 范围. (default "10.96.0.0/12")
--service-dns-domain string # 指定 service 的 dns 后缀, e.g. "myorg.internal". (default "cluster.local")
--skip-certificate-key- # 不打印 control-plane 用于加密证书的key.
--skip-phases strings # 跳过指定的阶段(phase)
--skip-token-print # 不打印 kubeadm init 生成的 default bootstrap token
--token string # 指定 node 和control plane 之间,简历双向认证的token ,格式为 [a-z0-9]{6}\.[a-z0-9]{16} - e.g. abcdef.0123456789abcdef
--token-ttl duration # token 自动删除的时间间隔。 (e.g. 1s, 2m, 3h). 如果设置为 '0', token 永不过期 (default 24h0m0s)
--upload-certs # 上传 control-plane 证书到 kubeadm-certs Secret

k8s 中有三个网段:node、service、pod,这三个网段千万不要冲突,否则通信会出现问题

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 多控制平面(多个master节点)
# 多个master通过HAProxy配置的负载均衡,再通过keepalived配置HAProxy的高可用,假设VIP为 10.0.0.1/24
kubeadm init --apiserver-advertise-address=10.0.0.71 \
--control-plane-endpoint=10.0.0.1 \
--apiserver-bind-port=6443 \
--kubernetes-version=v1.20.2 \
--pod-network-cidr=10.244.0.0/16 \
--service-cidr=10.10.0.0/16 \
--image-repository=registry.aliyuncs.com/google_containers

# 单控制平面(一个master节点)
# 对于单控制平面,controller-plane-endpoint默认同apiserver-advertise-address
kubeadm init --apiserver-advertise-address=10.0.0.71 \
--apiserver-bind-port=6443 \
--kubernetes-version=v1.20.2 \
--pod-network-cidr=10.244.0.0/16 \
--service-cidr=10.10.0.0/16 \
--image-repository=registry.aliyuncs.com/google_containers

kubeadm join

此命令用来初始化 Kubernetes 节点并将其加入集群

在执行完 kubeadm init 后,会在终端打印以后加入此集群时需要执行的kubeadm join 命令

参考:https://kubernetes.io/zh/docs/reference/setup-tools/kubeadm/kubeadm-join/

当节点加入 kubeadm 初始化的集群时,我们需要建立双向信任。 这个过程可以分解为发现TLS 引导两部分。

通常两个部分会使用相同的令牌。 在这种情况下可以使用 –token 参数,而不是单独指定每个令牌。

1
2
kubeadm join [api-server-endpoint] [flags]
kubeadm join [command]

command:

1
phase # 使用此命令可以调用连接工作流的单个阶段

api-server-endpoint:控制平面的 IP,如果是单控制平面,就是 master 节点的 IP,如果是多控制平面,就是 VIP

flags:

1

kubeadm alpha

kubeadm 处于测试阶段的命令,尽量不要用,kubeadm alpha --help 查看有哪些命令,v1.20.2 版本中只有一个:kubeconfig

kubeadm certs

1
kubeadm certs [command]

command:

1
2
3
4
certificate-key  # 生成证书密钥
check-expiration # 检查Kubernetes集群的证书过期情况
generate-csr # 生成密钥和证书签名请求
renew # 为Kubernetes集群更新证书
1
2
3
4
5
kubeadm certs check-expiration [flags]
# flags:
--cert-dir string
--config string
--kubeconfig string
1
2
3
4
5
kubeadm certs generate-csr [flags]
# flags:
--cert-dir string
--config string
--kubeconfig-dir string
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
kubeadm certs renew [command]
# command:
admin.conf # 更新嵌入在kubeconfig文件中的证书,供管理员和kubeadm本身使用
all # 续期所有可用证书
apiserver # 更新服务Kubernetes API的证书
apiserver-etcd-client # 更新apiserver访问etcd时使用的证书
apiserver-kubelet-client # 更新证书以便API服务器连接到kubelet
controller-manager.conf # 更新嵌入在kubeconfig文件中的证书,以便控制器管理器使用
etcd-healthcheck-client # 将活动探测的证书更新到healthcheck etcd
etcd-peer # 为etcd节点之间的通信更新证书
etcd-server # 续证供etcd使用
front-proxy-client # 更新前端代理客户端证书
scheduler.conf # 更新kubeconfig文件中嵌入的证书,以便调度器管理器使用

# 每个命令都可以使用 --help 查询帮助,例如:`kubeadm certs renew admin.conf --help`

kubeadm config

kubeadm init 执行期间,kubeadm 将 ClusterConfiguration 对象上传到你的集群的 kube-system 名字空间下 名为 kubeadm-config 的 ConfigMap 对象中。 然后在 kubeadm joinkubeadm resetkubeadm upgrade 执行期间读取此配置。 要查看此 ConfigMap,请调用 kubectl get cm -o yaml -n kube-system kubeadm-config

你可以使用 kubeadm config print 命令打印默认配置, 并使用 kubeadm config migrate 命令将旧版本的配置转化成新版本。 kubeadm config images listkubeadm config images pull 命令可以用来列出并拉取 kubeadm 所需的镜像。

1
2
kubeadm config [command]
kubeadm config [flags]

command:

1
2
3
images # 与kubeadm使用的容器镜像交互
migrate # 将本地旧版本的配置对象转换为最新支持的版本,而无需变更集群中的任何内容
print # 打印对象
1
2
3
4
kubeadm config images [command] [flags]
# command:
list # 打印 kubeadm 要使用的镜像列表。配置文件用于自定义任何镜像或镜像存储库。
pull # 拉取 kubeadm 使用的镜像
1
2
3
4
5
6
7
8
kubeadm config images list [flags]
# flags
--allow-missing-template-keys
--config string
-o, --experimental-output string
--feature-gates string
--image-repository string
--kubernetes-version string
1
2
3
4
5
6
7
kubeadm config images pull [flags]
# flags
--config string # kubeadm 配置文件的路径
--cri-socket string
--feature-gates string
--image-repository string
--kubernetes-version string
1
2
3
4
kubeadm config migrate [flags]
# flags:
--new-config string # 使用新的 API 版本生成的 kubeadm 配置文件的路径。默认输出到stdout。
--old-config string # 使用旧 API 版本且应转换的 kubeadm 配置文件的路径。此参数是必需的
1
2
3
4
kubeadm config print [command]
# command:
init-defaults # 打印默认的初始化配置,可以用于`kubeadm init`
join-defaults # 打印默认连接配置,可以用于`kubeadm join`
1
2
3
4
kubeadm config print init-defaults [flags]
kubeadm config print join-defaults [flags]
# flags
--component-configs strings

flags:

1
--kubeconfig string  # 与集群通信时要使用的kubeconfig文件。(默认/etc/kubernetes/admin.conf)

kubeadm reset

kubeadm reset 负责从使用 kubeadm initkubeadm join 命令创建的文件中清除节点本地文件系统。对于控制平面节点,reset 还从 etcd 集群中删除该节点的本地 etcd 堆成员,还从 kubeadm ClusterStatus 对象中删除该节点的信息。 ClusterStatus 是一个 kubeadm 管理的 Kubernetes API 对象,该对象包含 kube-apiserver 端点列表。

kubeadm reset phase 可用于执行上述工作流程的各个阶段。 要跳过阶段列表,你可以使用 –skip-phases 参数,该参数的工作方式类似于 kubeadm joinkubeadm init 阶段运行器

“reset” 执行以下阶段:

1
2
3
4
preflight              Run reset pre-flight checks
update-cluster-status Remove this node from the ClusterStatus object.
remove-etcd-member Remove a local etcd member.
cleanup-node Run cleanup node.
1
2
kubeadm reset [command]
kubeadm reset [flags]

command:

1
phase # 调用重置工作流的单个阶段
1
2
3
4
5
6
kubeadm reset phase [command]
# command
cleanup-node # Run cleanup node.
preflight # Run reset pre-flight checks
remove-etcd-member # Remove a local etcd member.
update-cluster-status # Remove this node from the ClusterStatus object.

flags:

1
2
3
4
5
6
--cert-dir string # 存储证书的目录路径。需要清空此目录。默认"/etc/kubernetes/pki"
--cri-socket string # 要连接的 CRI 套接字的路径。如果为空,则 kubeadm 将尝试自动检测此值;仅当安装了多个CRI 或具有非标准 CRI 插槽时,才使用此选项
-f, --force # 在不提示确认的情况下重置节点
--ignore-preflight-errors strings # 忽略检查过程中出现的错误信息,比如swap,如果为all就忽略所有
--kubeconfig string # 与集群通信时使用的 kubeconfig 文件,默认 /etc/kubernetes/admin.conf
--skip-phases strings # 要跳过的阶段列表,四个阶段

注意:如果使用了外部 etcd,kubeadm reset 将不会删除任何 etcd 中的数据

kubeadm token

用于管理 kubeadm join 使用的令牌

使用 kubeadm init 默认生成的 token 有效期 24 小时

1
2
3
4
5
6
7
8
9
10
# 生成新 token
$kubeadm token create --print-join-command
kubeadm join 10.0.0.171:6443 --token mud4vy.ygs3zdmuz6rwggrt --discovery-token-ca-cert-hash sha256:83da17177796380c6038dd8a76bc7050fd9c3c6b55d53a7f7a652817cc73c8e6
# 查看目前有效的token
$kubeadm token list
TOKEN TTL EXPIRES USAGES DESCRIPTION EXTRA GROUPS
mud4vy.ygs3zdmuz6rwggrt 23h 2021-01-23T23:46:03+08:00 authentication,signing <none> system:bootstrappers:kubeadm:default-node-token
# 查看 hash
$openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex | sed 's/^.* //'
83da17177796380c6038dd8a76bc7050fd9c3c6b55d53a7f7a652817cc73c8e6

kubeadm upgrade

用于升级 Kubernetes 集群到新版本

kubeadm version

用于打印 kubeadm 的版本信息

kubeadm help

部署 k8s 集群

部署规划图

学习环境,简单部署,准备三台主机,都是 centos7

角色 主机名 ip 地址
master1 master01.ljk.cn 10.0.0.71/24
node1 node01.ljk.cn 10.0.0.72/24
node2 node02.ljk.cn 10.0.0.73/24

安装 docker

注意:kubernetes 对 docker 的版本有要求,最新版本的 docker 一般来讲都不兼容

经过测试:kubernetes v1.20.2 最高支持 docker v19.03

在三台主机上均执行以下步骤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
yum install -y yum-utils device-mapper-persistent-data lvm2
yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
yum makecache fast
yum list docker-ce.x86_64 --showduplicates | sort -r # 搜索可用版本
yum install -y docker-ce-19.03.9 # 安装指定版本
# 加速 和 优化
mkdir -p /etc/docker
tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://3417nt4m.mirror.aliyuncs.com"],
"exec-opts":["native.cgroupdriver=systemd"]
}
EOF
systemctl enable --now docker.service

安装 Kubernetes

在三台主机上均执行以下步骤:

1
2
3
4
5
6
7
8
9
10
11
12
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
setenforce 0
yum install -y kubelet kubeadm kubectl
systemctl enable --now kubelet

部署 dns,或者直接修改/etc/hosts

在三台主机上均执行以下步骤:

1
2
3
4
5
# cat /etc/hosts
...
10.0.0.71 master01.ljk.cn
10.0.0.72 node01.ljk.cn
10.0.0.73 node02.ljk.cn

kubeadm init 初始化

集群初始化,而且集群初始化只需要初始化一次

注意:kubeadm init 必须在 master 节点上运行,任意一 master 节点即可

1
2
3
4
# 提前下载组件镜像,这一步可以不用执行,init的时候会自动下载,但是下载时间一般很长,最好还是提前下载
kubeadm config images pull --image-repository=registry.aliyuncs.com/google_containers --kubernetes-version=v1.20.2
# 预检查一下,出现问题则修复
[root@master01 docker]$kubeadm init phase preflight

初始化,两种方式:

1
2
3
4
5
6
7
8
# 方式一
[root@master01 ~]$kubeadm init --apiserver-advertise-address=10.0.0.71 \
--apiserver-bind-port=6443 \
--kubernetes-version=v1.20.2 \
--pod-network-cidr=10.244.0.0/16 \
--service-cidr=10.10.0.0/16 \
--image-repository=registry.aliyuncs.com/google_containers \
--ignore-preflight-errors=NumCPU # 学习环境,忽略cpu数量的检查
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
# 方式二
[root@master01 ~]$kubeadm config print init-defaults > /etc/kubernetes/kubeadmin-init.yaml
# 修改advertiseAddress、imageRepository、kubernetesVersion、dnsDomain、serviceSubnet
[root@master01 ~]$cat /etc/kubernetes/kubeadmin-init.yaml
apiVersion: kubeadm.k8s.io/v1beta2
bootstrapTokens:
- groups:
- system:bootstrappers:kubeadm:default-node-token
token: abcdef.0123456789abcdef
ttl: 24h0m0s
usages:
- signing
- authentication
kind: InitConfiguration
localAPIEndpoint:
advertiseAddress: 10.0.0.71
bindPort: 6443
nodeRegistration:
criSocket: /var/run/dockershim.sock
name: master01.ljk.cn
taints:
- effect: NoSchedule
key: node-role.kubernetes.io/master
---
apiServer:
timeoutForControlPlane: 4m0s
apiVersion: kubeadm.k8s.io/v1beta2
certificatesDir: /etc/kubernetes/pki
clusterName: kubernetes
#controlPlaneEndpoint: 10.0.0.1:6443 #多控制平面开启此项,基于 VIP 的 Endpoint
controllerManager: {}
dns:
type: CoreDNS
etcd:
local:
dataDir: /var/lib/etcd
imageRepository: registry.aliyuncs.com/google_containers
kind: ClusterConfiguration
kubernetesVersion: v1.20.2
networking:
dnsDomain: ljk.local
podSubnet: 10.244.0.0/16
serviceSubnet: 10.10.0.0/16
scheduler: {}

[root@master01 docker]$kubeadm init --config=/etc/kubernetes/kubeadmin-init.yaml --ignore-preflight-errors=NumCPU # 忽略检查cpu核心数,生产环境不要忽略
...
...
...
Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

Alternatively, if you are the root user, you can run:

export KUBECONFIG=/etc/kubernetes/admin.conf

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
https://kubernetes.io/docs/concepts/cluster-administration/addons/

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 10.0.0.71:6443 --token abcdef.0123456789abcdef \
--discovery-token-ca-cert-hash sha256:b794ecc7f2fc825b58e4e2108eab0dfa603c3d861de55bcc8c437949bed83fec

# 按照init成功后的提示
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
# 如果是root用户,也可以
export KUBECONFIG=/etc/kubernetes/admin.conf

# 安装网络插件 flannel,参考:https://github.com/coreos/flannel
# 这个链接可能需要科学上网
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

补充:如果是多控制平面,kubeadm init 需要添加 –control-plane-endpoint 参数,设置访问多个 master 的 VIP,通常是 keepalived+HAProxy 配置多个 master 的高可用

1
2
3
4
5
6
7
8
[root@master01 ~]$kubeadm init --apiserver-advertise-address=10.0.0.71 \
--control-plane-endpoint=10.0.0.1 \
--apiserver-bind-port=6443 \
--kubernetes-version=v1.20.2 \
--pod-network-cidr=10.244.0.0/16 \
--service-cidr=10.10.0.0/16 \
--image-repository=registry.aliyuncs.com/google_containers \
--ignore-preflight-errors=NumCPU

kubeadm join node 加入 k8s 集群

加入集群的 kubeadm join 命令在 kubeadm init 完成后会自动打印出来,如果将来 token 和 hash 过期失效了,使用 kubeadm token create --print-join-command 重新生成即可

1
2
3
4
5
6
7
8
# node1
[root@node01 kubernetes]$kubeadm join 10.0.0.71:6443 \
--token abcdef.0123456789abcdef \
--discovery-token-ca-cert-hash sha256:b794ecc7f2fc825b58e4e2108eab0dfa603c3d861de55bcc8c437949bed83fec
# node2
[root@node02 ~]$kubeadm join 10.0.0.71:6443 \
--token abcdef.0123456789abcdef \
--discovery-token-ca-cert-hash sha256:b794ecc7f2fc825b58e4e2108eab0dfa603c3d861de55bcc8c437949bed83fec

查看是否加入成功:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@master01 ~]$kubectl get nodes
NAME STATUS ROLES AGE VERSION
master01.ljk.cn Ready control-plane,master 53m v1.20.2
node01.ljk.cn Ready <none> 6m24s v1.20.2
node02.ljk.cn Ready <none> 10m v1.20.2

# 在每个节点上都生成一个flannel网卡,或者说是网络隧道,保障集群内部的通信
[root@master01 ~]$ip a # master
...
4: flannel.1: <BROADCAST,MULTICAST> mtu 1450 qdisc noop state DOWN group default
link/ether 26:69:fd:2f:7a:3f brd ff:ff:ff:ff:ff:ff
[root@node01 kubernetes]$ip a # node1
...
4: flannel.1: <BROADCAST,MULTICAST> mtu 1450 qdisc noop state DOWN group default
link/ether a2:74:d6:61:d5:5c brd ff:ff:ff:ff:ff:ff
[root@node02 ~]$ip a # node2
...
4: flannel.1: <BROADCAST,MULTICAST> mtu 1450 qdisc noop state DOWN group default
link/ether fe:83:2b:fc:1e:01 brd ff:ff:ff:ff:ff:ff

部署 Web 服务 Dashboard

github 地址:https://github.com/kubernetes/dashboard/releases

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
# 1.下载配置清单
$wget https://raw.githubusercontent.com/kubernetes/dashboard/v2.1.0/aio/deploy/recommended.yaml
# 2.修改配置清单
$vim recommended.yaml
...
spec:
type: NodePort # 添加此行
ports:
- port: 443
targetPort: 8443
nodePort: 30002 # 添加此行
selector:
k8s-app: kubernetes-dashboard
...
# 3.创建资源对象
kubectl apply -f ./recommended.yaml
# 4.查看
$kubectl get pod -n kubernetes-dashboard
NAME READY STATUS RESTARTS AGE
dashboard-metrics-scraper-79c5968bdc-5dv7w 1/1 Running 0 88m
kubernetes-dashboard-7448ffc97b-rg77t 1/1 Running 0 88m
# 5.创建管理 dashboard 的 service 账号
$cat admin-user.yml
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
$kubectl apply -f admin-user.yml
# 6.查看 admin-user 的 token
$kubectl get secrets -A | grep admin-user
$kubectl describe secrets -n kubernetes-dashboard admin-user-token-wpz7j
Name: admin-user-token-wpz7j
Namespace: kubernetes-dashboard
Labels: <none>
Annotations: kubernetes.io/service-account.name: admin-user
kubernetes.io/service-account.uid: 613c9277-f124-4622-88c9-714dc2479602

Type: kubernetes.io/service-account-token

Data
====
ca.crt: 1066 bytes
namespace: 20 bytes
token: eyJhbGciOiJSUzI1NiIsImtpZCI6IjJ2THVKYzd4Q3hVeVI5QWVCMk5YUU5kbE9lRzJjZ1hHZkV0WndQUGczWmcifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlcm5ldGVzLWRhc2hib2FyZCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhZG1pbi11c2VyLXRva2VuLXdwejdqIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImFkbWluLXVzZXIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiI2MTNjOTI3Ny1mMTI0LTQ2MjItODhjOS03MTRkYzI0Nzk2MDIiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZXJuZXRlcy1kYXNoYm9hcmQ6YWRtaW4tdXNlciJ9.N7Hcf9JIgV5p_j8-YYJHMDrqGkGtsalTxbabTjXASyoR63gmvNmekPACQ9EDwu1MF2du3wkyReVb_aYD11tHw4XsNbfnoaytYqcQ9U8lhLXd8A7wlBnK8_EBa5-XP0-nRRSw7gUsG6s7xvrviWcndNO0nIUPgS--B1G8S-AR_Y0CJRzFF4FL0zhZvxCxSDwHO2OrjKpbxhuZOgzhOI_CXCEMghsCyUUFeNlRj2F-YulzVNLdJSzIh0UMyvoKVv6ylesRtyUJK8lo3tpcC0K4hTKsWYXHgibto024tT4D1KtYLqNhEhI4PkYogZQTsG3-ggVo5Nry5m8AL0CKCD0xoA

最后,打开浏览器,输入 https://10.0.0.171:30002/,除了10.0.0.171,10.0.0.71 和 10.0.0.72 也可以,然后 copy 刚才生产的 admin-user 用户的 token,成功登陆

k8s 集群升级

小版本升级,一般不会有很大更改,经过充分测试,直接升级即可

大版本升级,风险较大,可以像小版本一样,经过测试,然后直接升级;但是更推荐以下升级方式:

  1. 在备用的服务器上再部署一套 k8s 集群,使用最新的 k8s 版本

  2. 把业务离线导入新的 k8s 集群

  3. 经过测试后,切换负载均衡到新的 k8s 集群

  4. 下线的旧 k8s 集群,直接重装系统,安装最新的 ubuntu 或者 centos,然后作为 node 加入到新的 k8s 集群

  5. 升级完成

这种方法是比较稳妥的,只是成本比较高,需要有充足的备用的服务器,如果目前的 k8s 集群使用 30 台服务器,那备用的服务器差不多也得 20 台

升级 master

先升级 kubeadm,kubeadm 是 k8s 升级的“准升证”

  1. 验证当 k8s 前版本

    1
    $kubeadm version
  2. 各 master 安装指定新版本 kubeadm

    1
    2
    3
    4
    $apt-cache madison kubeadm #查看 k8s 版本列表
    $apt-get install kubeadm=1.17.4-00 #安装新版本 kubeadm
    $kubeadm version #验证 kubeadm 版本
    $apt install kubelet=1.17.4-00 kubectl=1.17.4-00 # kubectl 和 kubelet 也升级一下
  3. kubeadm 升级命令使用帮助

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    $kubeadm upgrade --help
    Upgrade your cluster smoothly to a newer version with this command

    Usage:
    kubeadm upgrade [flags]
    kubeadm upgrade [command]

    Available Commands:
    apply Upgrade your Kubernetes cluster to the specified version
    diff Show what differences would be applied to existing static pod manifests. See also: kubeadm upgrade apply --dry-run
    node Upgrade commands for a node in the cluster
    plan Check which versions are available to upgrade to and validate whether your current cluster is upgradeable. To skip the internet check, pass in the optional [version] parameter
  4. 升级计划

    1
    kubeadm upgrade plan #查看升级计划

  5. 开始升级

    1
    2
    3
    $kubeadm upgrade apply v1.17.4 #master1
    $kubeadm upgrade apply v1.17.4 #master2
    $kubeadm upgrade apply v1.17.4 #master3

  6. 验证镜像

    1
    $docker image ls

升级 node

升级客户端服务 kubectl kubelet

  1. 验证当前 node 版本信息

    1
    $kubectl get node
  2. 升级各 node 节点配置文件

    1
    $kubeadm upgrade node --kubelet-version v1.17.4  # master上执行
  3. 各 Node 节点升级 kubelet、kubeadm、kubectl 二进制包

    1
    $apt install kubelet=1.17.4-00 kubeadm=1.17.4-00 kubectl=1.17.4-00

测试运行 Nginx+Tomcat

Namespace

并非所有对象都在名字空间中,大多数 kubernetes 资源(例如 Pod、Service、deployment 等)都位于某些 Namespace 中。 但是 Namespace 资源本身并不在 Namespace 中。而且底层资源,例如 Node 和 persistentVolumes 不属于任何名字空间

查看哪些资源在 Namespace 中,哪些不在 Namespace 中:

1
2
3
4
5
# 位于名字空间中的资源
kubectl api-resources --namespaced=true

# 不在名字空间中的资源
kubectl api-resources --namespaced=false

Pod

以应用(Pod)为中心:

  • pop 是容器集,是 k8s 的最小运行单元,一个 pop 中的所有容器必须运行在同一 node 上
  • 运行多个容器的话,这些容器是一起被调度的
  • Pod 的生命周期是短暂的,不会自愈,是用完就销毁的实体
  • 一般通过 Controller 来创建和管理 pod

建立 pod

p83,4.5.2 pod 的创建过程

一个 pod 就像是一个虚拟机:

pause 是 pod 的基础设施容器,这个容器的作用是给 pod 内部容器提供可被加入和共享的 net、ipc、uts、namespace,同时我们向一个 pod 附加 volume 时,也是附加在 pause 之上,从而可以被共享给一个 pod 内的多个容器

Pod 的生命周期

初始化容器、启动前操作、就绪探针、存活探针、删除 pod 操作

livenessProb

存活探针,检测应用是否发生故障,检测失败重启 pod

readinessProbe

就绪探针,检测 pod 启动之后应用是否就绪,是否可以提供服务,检测成功,pod 才开始接收流量

控制器(controller)

控制器有数十种,最常用的是 deployment:无状态应用编排工具

控制器 简写 简述
ReplicationController rc 第一代 pod 副本控制器
ReplicaSet rs 第二代 pod 副本控制器
Deployment deploy 第三代 pod 控制器

ReplicationController

https://kubernetes.io/zh/docs/concepts/workloads/controllers/replicationcontroller/
https://kubernetes.io/zh/docs/concepts/overview/working-with-objects/labels/

副本控制器 selector =、!=

ReplicaSet

https://kubernetes.io/zh/docs/concepts/workloads/controllers/replicaset/

副本控制集,和副本控制器的区别是:selector 还支持 in、notin

Deployment

https://kubernetes.io/zh/docs/concepts/workloads/controllers/deployment/

比 rs 更高一级的控制器,除了有 rs 的功能之外,还有很多高级功能,,比如说最重要的:滚动升级、回滚等

DaemonSet

DaemonSet 确保全部(或者某些)节点上运行一个 Pod 的副本。 当有节点加入集群时, 也会为他们新增一个 Pod 。 当有节点从集群移除时,这些 Pod 也会被回收。删除 DaemonSet 将会删除它创建的所有 Pod

DaemonSet 的一些典型用法:

  • 在每个节点上运行集群守护进程
  • 在每个节点上运行日志收集守护进程
  • 在每个节点上运行监控守护进程

一种简单的用法是为每种类型的守护进程在所有的节点上都启动一个 DaemonSet。 一个稍微复杂的用法是为同一种守护进程部署多个 DaemonSet;每个具有不同的标志, 并且对不同硬件类型具有不同的内存、CPU 要求

Service

pod 重建之后 ip 就变了,pod 之间直接访问会有问题,service 就是用来解决这个问题

service 解耦了服务和应用

一般常用的有两种:

  1. k8s 集群内的 service:selector 指定 pod,自动创建 Endpoints
  2. k8s 集群外的 service:手动创建 Endpoints,指定外部服务的 ip,端口和协议

kube-proxy 和 service 的关系:
kube-proxy 监听着 k8s-apiserver,一旦 service 资源发生变化(调 k8s-api 修改 service 信息),kube-proxy 就会生成对应的负载调度的调整,这样就保证 service 的最新状态

卷(volume)

volume:https://kubernetes.io/zh/docs/concepts/storage/volumes/
emptydir:https://kubernetes.io/zh/docs/concepts/storage/volumes/#emptydir
hostpath:https://kubernetes.io/zh/docs/concepts/storage/volumes/#hostpath
nfs:https://kubernetes.io/zh/docs/concepts/storage/volumes/#nfs
configmap:https://kubernetes.io/zh/docs/concepts/storage/volumes/#configmap

卷类型&emsp; &emsp; &emsp; &emsp; &emsp; 简介
emptyDir 挂载一个空目录,pod 删除时,卷也会被删除
hostPath docker -v 实现一样的效果
挂载文件或目录挂载,pod 删除的时候,卷不会被删除,但是 Pod 和 node 是绑定的,nfs 等共享存储可以解决这个问题
nfs 等共享存储 挂载 NFS(网络文件系统),当删除 Pod 时,nfs 卷被卸载,但是内容保留
NFS 可以被多个 Pod 同时挂载
Configmap Why:配置信息和镜像解耦,避免每次更新配置都得重新打镜像
What:将配置信息放到 configmap 对象中,然后在 pod 的对象中导入 configmap 对象,实现导入配置的操作
How:声明一个 ConfigMap 的对象,作为 Volume 挂载到 pod 中

PV / PVC

https://kubernetes.io/zh/docs/concepts/storage/persistent-volumes/

存储的管理是一个与计算实例的管理完全不同的问题。PersistentVolume 子系统为用户和管理员提供了一组 API,将存储如何供应的细节从其如何被使用中抽象出来。 为了实现这点,我们引入了两个新的 API 资源:PersistentVolume 和 PersistentVolumeClaim

1
2
PV:PersistentVolume,持久卷
PVC:PersistentVolumeClaim,持久卷申领

对于有状态服务,例如 MySQL、zookeeper,搭建集群的时候,因为不能共享存储,所以可以给每个节点挂载一个 nfs 存储,但是更推荐在 nfs 上再封装一层 PV / PVC

PV / PVC 用于有状态的服务,例如 MySQL、zookeeper,nfs 用于无状态的服务,例如 nginx、tomcat

PV / PVC 通常和 Statefulset 搭配使用

Statefulset -> Operator

https://kubernetes.io/zh/docs/concepts/workloads/controllers/statefulset/

deployment 是无状态应用编排工具,而 statefulset 是有状态应用编排工具

statefulset 本质上是 Deployment 的一种变体,为了解决有状态服务的问题,statefulset 管理的 Pod 拥有固定的 Pod 名称,主机名,启停顺序,在 statefulset 中,Pod 名字称为网络标识(hostname),还必须要用到共享存储

在 deployment 中,与之对应的服务是 service,而在 statefulset 中与之对应的是 headless service,即无头服务,与 service 的区别就是它没有 Cluster IP,解析它的名称时将返回该 headless service 对应的全部 Pod 的 Endpoint 列表

StatefulSet 特点:

  • 给每个 pdo 分配固定且唯一的网络标识符
  • 每个pod 分配固定且持久化的外部存储
  • 对 pod 进行有序的部署和扩展
  • 对 pod 进有序的删除和终止
  • 对 pod 进有序的自动滚动更新

StatefulSet 的组成部分:

  • Headless Service:用来定义 Pod 网络标识( DNS domain)
  • StatefulSet:定义具体应用,有多少个 Pod 副本,并为每个 Pod 定义了一个域名
  • volumeClaimTemplates: 存储卷申请模板,创建 PVC,指定 pvc 名称大小,将自动创建 pvc,且 pvc 必须由存储类供应

Operator

https://kubernetes.io/zh/docs/concepts/extend-kubernetes/operator/

不同的有状态应用的运维操作过程差别巨大,因此 StatefulSet 控制器本身几乎无法为此种类型的应用提供完善的通用管理控制机制,现实中的各种有状态应用通常是使用专用的自定义控制器专门封装特定的运维操作流程,这些自定义控制器统称为 Operator

Operator 是 kubernetes 的扩展软件,Operator 是 Kubernetes API 的客户端,充当 定制资源 的控制器

Socket 套接字

套接字 Socket 是进程间通信 IPC 的一种实现,允许位于不同主机(或同一主机)上不同进程之间进行通信和数据交换,SocketAPI 出现于 1983 年 BSD 4.2 实现

在建立通信连接的每一端,进程间的传输要有两个标志:IP 地址和端口号,合称为套接字地址 socket address

客户机套接字地址定义了一个唯一的客户进程
服务器套接字地址定义了一个唯一的服务器进程

Socket API

封装了内核中所提供的 socket 通信相关的系统调用

Socket Domain:根据其所使用的地址

1
2
3
AF_INET  # Address Family,IPv4
AF_INET6 # IPv6
AF_UNIX # 同一主机上不同进程之间通信时使用

Socket Type:根据使用的传输层协议

1
2
3
SOCK_STREAM  # 流,tcp套接字,可靠地传递、面向连接
SOCK_DGRAM # 数据报,udp套接字,不可靠地传递、无连接
SOCK_RAW # 裸套接字,无须tcp或udp,APP直接通过IP包通信

客户/服务器程序的套接字函数

1
2
3
4
5
6
7
8
socket()   # 创建一个套接字
bind() # 绑定IP和端口
listen() # 监听
accept() # 接收请求
connect() # 请求连接建立
write() # 发送
read() # 接收
close() # 关闭连接

http 协议及报文头部结构

Method 方法

请求方法,标明客户端希望服务器对资源执行的动作

  • GET: 从服务器获取一个资源
  • HEAD: 只从服务器获取文档的响应首部
  • POST: 向服务器输入数据,通常会再由网关程序继续处理
  • PUT: 将请求的主体部分存储在服务器中,如上传文件
  • DELETE: 请求删除服务器上指定的文档
  • TRACE: 追踪请求到达服务器中间经过的代理服务器
  • OPTIONS:请求服务器返回对指定资源支持使用的请求方法
  • CONNECT:建立一个到由目标资源标识的服务器的隧道
  • PATCH:用于对资源应用部分修改

status 状态码

参考资料:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status

1
2
3
4
5
1xx:100-101 信息提示
2xx:200-206 成功
3xx:300-307 重定向
4xx:400-415 错误类信息,客户端错误
5xx:500-505 错误类信息,服务器端错误
1
2
3
4
5
6
7
8
9
10
11
12
200: 成功,请求数据通过响应报文的entity-body部分发送;OK
301: 永久重定向;Moved Permanently
302: 临时重定向 Moved Temporarily
304: 客户端发出了条件式请求,但服务器上的资源未曾发生改变,则通过响应此响应状态码通知客户端;Not Modified
307: 浏览器内部重定向
401: 需要输入账号和密码认证方能访问资源;Unauthorized
403: 请求被禁止;Forbidden
404: 服务器无法找到客户端请求的资源;Not Found
500: 服务器内部错误;Internal Server Error
502: 代理服务器从后端服务器收到了一条伪响应,如无法连接到网关;Bad Gateway
503: 服务不可用,临时服务器维护或过载,服务器无法处理请求
504: 网关超时

关于 301 和 302:

用户输入了你不希望输入的 url 或者说是没用的 url,用 301 重定向,例如 http 转到 https、旧域名转到新域名;
因为业务逻辑临时跳转,用 302 重定向,例如用户直接访问管理后台就临时跳转到登录页面

web 相关工具

wget

1
wget [OPTION]... [URL]...
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
#启动
-V,-version 显示wget的版本后退出
-h,-help 打印语法帮助
-b,-background 启动后转入后台执行
-e,-execute=COMMAND 执行`.wgetrc'格式的命令,wgetrc格式参见/etc/wgetrc或~/.wgetrc

#记录和输入文件
-o,-output-file=FILE 把记录写到FILE文件中
-a,-append-output=FILE 把记录追加到FILE文件中
-d,-debug 打印调试输出
-q,-quiet 安静模式(没有输出)
-v,-verbose 冗长模式(这是缺省设置)
-nv,-non-verbose 关掉冗长模式,但不是安静模式
-i,-input-file=FILE 下载在FILE文件中出现的URLs
-F,-force-html 把输入文件当作HTML格式文件对待
-B,-base=URL 将URL作为在-F -i参数指定的文件中出现的相对链接的前缀
-sslcertfile=FILE 可选客户端证书
-sslcertkey=KEYFILE 可选客户端证书的KEYFILE
-egd-file=FILE 指定EGD socket的文件名

#下载
-bind-address=ADDRESS 指定本地使用地址(主机名或IP,当本地有多个IP或名字时使用)
-t,-tries=NUMBER 设定最大尝试链接次数(0 表示无限制).
-O -output-document=FILE 把文档写到FILE文件中
-nc,-no-clobber 不要覆盖存在的文件或使用.#前缀
-c,-continue 接着下载没下载完的文件
-progress=TYPE 设定进程条标记
-N,-timestamping 不要重新下载文件除非比本地文件新
-S,-server-response 打印服务器的回应
-spider 不下载任何东西
-T,-timeout=SECONDS 设定响应超时的秒数
-w,-wait=SECONDS 两次尝试之间间隔SECONDS秒
-waitretry=SECONDS 在重新链接之间等待1…SECONDS秒
-random-wait 在下载之间等待0…2*WAIT秒
-Y,-proxy=on/off 打开或关闭代理
-Q,-quota=NUMBER 设置下载的容量限制
-limit-rate=RATE 限定下载输率
#目录
-nd -no-directories 不创建目录
-x,-force-directories 强制创建目录
-nH,-no-host-directories 不创建主机目录
-P,-directory-prefix=PREFIX 将文件保存到目录 PREFIX/…
-cut-dirs=NUMBER 忽略 NUMBER层远程目录
#HTTP 选项
-http-user=USER 设定HTTP用户名为 USER.
-http-passwd=PASS 设定http密码为 PASS.
-C,-cache=on/off 允许/不允许服务器端的数据缓存 (一般情况下允许).
-E,-html-extension 将所有text/html文档以.html扩展名保存
-ignore-length 忽略 'Content-Length'头域
-header=STRING 在headers中插入字符串 STRING
-proxy-user=USER 设定代理的用户名为 USER
-proxy-passwd=PASS 设定代理的密码为 PASS
-referer=URL 在HTTP请求中包含 `Referer: URL'
-s,-save-headers 保存HTTP头到文件
-U,-user-agent=AGENT 设定代理的名称为 AGENT而不是 Wget/VERSION.
-no-http-keep-alive 关闭 HTTP活动链接 (永远链接).
-cookies=off 不使用 cookies.
-load-cookies=FILE 在开始会话前从文件 FILE中加载cookie
-save-cookies=FILE 在会话结束后将 cookies保存到 FILE文件中

#FTP 选项
-nr,-dont-remove-listing 不移走 `.listing'文件
-g,-glob=on/off 打开或关闭文件名的 globbing机制
-passive-ftp 使用被动传输模式 (缺省值).
-active-ftp 使用主动传输模式
-retr-symlinks 在递归的时候,将链接指向文件(而不是目录)

#递归下载
-r,-recursive 递归下载--慎用!
-l,-level=NUMBER 最大递归深度 (inf 或 0 代表无穷).
-delete-after 在现在完毕后局部删除文件
-k,-convert-links 转换非相对链接为相对链接
-K,-backup-converted 在转换文件X之前,将之备份为 X.orig
-m,-mirror 等价于 -r -N -l inf -nr.
-p,-page-requisites 下载显示HTML文件的所有图片

#递归下载中的包含和不包含(accept/reject)
-A,-accept=LIST 分号分隔的被接受扩展名的列表
-R,-reject=LIST 分号分隔的不被接受的扩展名的列表
-D,-domains=LIST 分号分隔的被接受域的列表
-exclude-domains=LIST 分号分隔的不被接受的域的列表
-follow-ftp 跟踪HTML文档中的FTP链接
-follow-tags=LIST 分号分隔的被跟踪的HTML标签的列表
-G,-ignore-tags=LIST 分号分隔的被忽略的HTML标签的列表
-H,-span-hosts 当递归时转到外部主机
-L,-relative 仅仅跟踪相对链接
-I,-include-directories=LIST 允许目录的列表
-X,-exclude-directories=LIST 不被包含目录的列表
-np,-no-parent 不要追溯到父目录

常用选项:

1
2
3
4
5
-q 静默模式
-c 断点续传
-P /path 保存在指定目录
-O filename 保存为指定文件名,filename 为 - 时,发送至标准输出
--limit-rate= 指定传输速率,单位K,M等

curl

curl 是基于 URL 语法在命令行方式下工作的文件传输工具,它支持 FTP,FTPS,HTTP,HTTPS,GOPHER,TELNET,DICT,FILE 及 LDAP 等协议。curl 支持 HTTPS 认证,并且支持 HTTP 的 POST、PUT 等方法,FTP 上传,kerberos 认证,HTTP 上传,代理服务器,cookies,用户名/密码认证,下载文件断点续传,上载文件断点续传,http 代理服务器管道( proxy tunneling),还支持 IPv6,socks5 代理服务器,通过 http 代理服务器上传文件到 FTP 服务器等,功能十分强大

1
curl [options] [URL...]
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
-A/--user-agent <string> 设置用户代理发送给服务器
-e/--referer <URL> 来源网址
--cacert <file> CA证书 (SSL)
-k/--insecure 允许忽略证书进行 SSL 连接
--compressed 要求返回是压缩的格式
-H/--header "key:value” 自定义首部字段传递给服务器
-i 显示页面内容,包括报文首部信息
-I/--head 只显示响应报文首部信息
-D/--dump-header <file>将url的header信息存放在指定文件中
--basic 使用HTTP基本认证
-u/--user <user[:password]>设置服务器的用户和密码
-L 如果有3xx响应码,重新发请求到新位置
-O 使用URL中默认的文件名保存文件到本地
-o <file> 将网络文件保存为指定的文件中
--limit-rate <rate> 设置传输速度
-0/--http1.0 数字0,使用HTTP 1.0
-v/--verbose 更详细
-C 选项可对文件使用断点续传功能
-c/--cookie-jar <file name> 将url中cookie存放在指定文件中
-x/--proxy <proxyhost[:port]> 指定代理服务器地址
-X/--request <command> 向服务器发送指定请求方法
-U/--proxy-user <user:password> 代理服务器用户和密码
-T 选项可将指定的本地文件上传到FTP服务器上
--data/-d 方式指定使用POST方式传递数据
-s --silent Silent mode
-b name=data 从服务器响应set-cookie得到值,返回给服务器
-w <format> 显示相应的指定的报文信息,如:%{http_code},%{remote_ip}等
-m, --max-time <time> 允许最大传输时间

httpie

httpie 工具是现代的 HTTP 命令行客户端,它能通过命令行界面与 Web 服务进行交互。它提供一个简单的 http 命令,允许使用简单而自然的语法发送任意的 HTTP 请求,并会显示彩色的输出

httpie 能用于测试、调试及与 HTTP 服务器交互

  • 具表达力的和直观语法
  • 格式化的及彩色化的终端输出
  • 内置 JSON 支持
  • 表单和文件上传
  • HTTPS、代理和认证任意请求数据
  • 自定义头部
  • 持久化会话
  • 类似 wget 的下载
  • 支持 Python 2.7 和 3.x

官方网站:https://httpie.org
安装:基于 EPEL(CentOS 8 目前还不支持)

1
[root@centos7 ~]#yum install httpie -y

范例:

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
# 显示信息(包含响应头200)
http www.magedu.com

# 显示详细的请求(包含请求和返回头200)
http -v www.magedu.com

# 只显示Header
http -h www.magedu.com
http --head www.magedu.com
http --header www.magedu.com
http --headers www.magedu.com

# 只显示Body
http -b www.magedu.com
http --body magedu.com

# 下载文件
http -d www.magedu.com

# 模拟提交表单
http -f POST www.magedu.com username='wang'

# 请求删除的方法
http DELETE www.magedu.com

# 传递JSON数据请求(默认就是JSON数据请求)
http PUT www.magedu.com username='wang' password='magedu'

# 如果JSON数据存在不是字符串则用:=分隔,例如
http PUT www.magedu.com username='wang' password='magedu' age:=30 a:=true streets:='["a", "b"]'

# 模拟Form的Post请求, Content-Type: application/x-www-form-urlencoded;
charset=utf-8
http --form POST www.magedu.com username='wang'

# 模拟Form的上传, Content-Type: multipart/form-data
http -f POST www.magedu.com/jobs username='wang' file@~/test.pdf

# 修改请求头, 使用:分隔
http www.magedu.com User-Agent:magedu-agent/1.0 'Cookie:a=b;b=c' Referer:http://www.google.com/

# 认证
http -a username:password www.magedu.com
http -A basic -a username:password www.magedu.com

# 使用http代理
http --proxy=http:http://172.16.0.100:8081 proxy.magedu.com
http --proxy=http:http://user:pass@172.16.0.100:8081 proxy.magedu.com
http --proxy=https:http://172.16.0.100:8118 proxy.magedu.com
http --proxy=https:http://user:pass@172.16.0.100:8118 proxy.magedu.com

范例:查看信息及相应头

1
2
3
4
5
6
7
8
9
10
11
12
[root@centos7 ~]#http 192.168.8.8/test.html
HTTP/1.1 200 OK
Accept-Ranges: bytes
Connection: Keep-Alive
Content-Length: 30
Content-Type: text/html; charset=UTF-8
Date: Thu, 07 Nov 2019 09:09:34 GMT
ETag: "1e-596be05bc9a34"
Keep-Alive: timeout=5, max=100
Last-Modified: Thu, 07 Nov 2019 09:09:27 GMT
Server: Apache/2.4.37 (centos)
<strong>马哥教育</strong>

范例:查看请示和响应头部及信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@centos7 ~]#http -v 192.168.8.8/test.html
GET /test.html HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Host: 192.168.8.8
User-Agent: HTTPie/0.9.4

HTTP/1.1 200 OK
Accept-Ranges: bytes
Connection: Keep-Alive
Content-Length: 30
Content-Type: text/html; charset=UTF-8
Date: Thu, 07 Nov 2019 09:09:39 GMT
ETag: "1e-596be05bc9a34"
Keep-Alive: timeout=5, max=100
Last-Modified: Thu, 07 Nov 2019 09:09:27 GMT
Server: Apache/2.4.37 (centos)
<strong>马哥教育</strong>

范例:查看响应报文头部

1
2
3
4
5
6
7
8
9
10
11
[root@centos7 ~]#http HEAD http://www.magedu.com
HTTP/1.1 200 OK
Connection: keep-alive
Content-Encoding: gzip
Content-Type: text/html; charset=UTF-8
Date: Thu, 07 Nov 2019 08:09:49 GMT
Link: <http://www.magedu.com/wp-json/>; rel="https://api.w.org/"
Link: <http://www.magedu.com/>; rel=shortlink
Server: Tengine
Vary: Accept-Encoding
Vary: Accept-Encoding, Cookie

范例: 查看请求和响应报文头部

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@centos7 ~]#http -p Hh http://www.magedu.com
GET / HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Host: www.magedu.com
User-Agent: HTTPie/0.9.4

HTTP/1.1 200 OK
Cache-Control: max-age=3, must-revalidate
Connection: keep-alive
Content-Encoding: gzip
Content-Type: text/html; charset=UTF-8
Date: Thu, 07 Nov 2019 03:44:14 GMT
Server: Tengine
Transfer-Encoding: chunked
Vary: Accept-Encoding
Vary: Accept-Encoding, Cookie

范例:指定请求头部的首部字段

1
2
3
4
5
6
7
8
9
[root@centos7 ~]#http -p H http://www.magedu.com User-Agent:wangtest
Referer:http://www.baidu.com
GET / HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Host: www.magedu.com
Referer: http://www.baidu.com
User-Agent: wangtest

范例:下载资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#方法1
[root@centos7 ~]#http http://www.magedu.com/wp-content/uploads/2018/12/2018122312035677.png > logo.png
#方法2
[root@centos7 ~]#file logo.png
logo.png: PNG image data, 411 x 127, 8-bit/color RGBA, non-interlaced
[root@centos7 ~]#http --download http://www.magedu.com/wp-content/uploads/2018/12/2018122312035677.png
HTTP/1.1 200 OK
Accept-Ranges: bytes
Cache-Control: max-age=604800
Connection: keep-alive
Content-Length: 8983
Content-Type: image/png
Date: Thu, 07 Nov 2019 08:20:44 GMT
ETag: "5c1f79ac-2317"
Expires: Thu, 14 Nov 2019 08:20:44 GMT
Last-Modified: Sun, 23 Dec 2018 12:03:56 GMT
Server: Tengine
Vary: Accept-Encoding

Downloading 8.77 kB to "2018122312035677.png"
Done. 8.77 kB in 0.00091s (9.46 MB/s)

范例:用 POST 方法提交 json 格式的数据

1
[root@centos7 ~]#http http://www.magedu.com user=wang password=magedu

范例:用 POST 方法,指交表单数据

1
[root@centos7 ~]#http -f POST http://www.magedu.com user=wang password=magedu

压力测试工具

ab 来自 httpd-tools 包

1
ab [OPTIONS] URL

option:

  • -n:总请求数
  • -c:模拟的并发数
  • -k:以持久连接模式测试

默认情况下,容器没有资源的使用限制,docker 提供了控制容器使用资源的方法,可以限制容器使用多少内存或 CPU 等, 在 docker run 命令的运行时配置标志实现资源限制功能。

容器的内存限制

为了运行效率,建议关闭交换内存,如果内存不够了,打报告采购就好了,而且 k8s 也禁止使用交换内存

在容器中使用 free 命令,看到的是宿主机的内存情况

内存相关配置项 说明
-m | –mermory= 容器可以使用的最大内存,硬限制,最小 4m,此项常用
–mermory-swap 交换内存相关的配置项,如果宿主机关闭了交换内存,建议忽略此项

范例:

1
2
3
# 单位:b、k、m、g
# 限制容器的内存不超过300m
root@u3:~# docker container run -it -m 300m harbor.ljk.org/library/alpine:v1

容器的 CPU 限制

docker 仓库分为公有云仓库 和 私有云仓库

公有云仓库:

  • 官方
  • 阿里云等第三方仓库,推荐

私有云仓库:

  • docker registory
  • docker harbor,推荐

阿里云 docker 仓库

  1. 访问凭证
  2. 命名空间
  3. 镜像仓库

范例:

1
2
3
4
5
6
7
8
9
10
# 1. 浏览器的登录阿里云,创建命名空间107,镜像仓库可以创建也可以不创建,如果不创建,push的时候会自动创建
# 2. 登录阿里云,交互式,密码存储在~/.docker/config.json
docker login --username=1076956365@qq.com registry.cn-beijing.aliyuncs.com
# 3. 给镜像打标签,注意格式
docker tag nginx:1.22.1 registry.cn-beijing.aliyuncs.com/lujk/nginx:1.22.1
# 4. 上传到阿里云的docker镜像仓库
docker push registry.cn-beijing.aliyuncs.com/lujk/nginx:1.22.1
# 5. 刷新网站页面,如果没有看到镜像,可能是区域设置不对
# 6. 其他机器下载镜像
docker pull registry.cn-beijing.aliyuncs.com/lujk/nginx:1.22.1

私有云单机仓库 Docker Registry

官方文档:https://docs.docker.com/registry/

部署文档:https://github.com/docker/docs/blob/main/registry/deploying.md

https://blog.csdn.net/weixin_41605937/article/details/122367707

服务端

1. 生成证书

一路回车就可以

1
$ openssl req -new -x509 -days 3650 -nodes -out ./ssl/certs/reg.cntv.local.crt -keyout ./ssl/private/reg.cntv.local.key

2.创建授权的 registry 用户和密码

1
2
3
4
5
6
7
8
# 1. 创建授权用户密码使用目录
root@lujinkai-pc:~$ mkdir -p /etc/docker/auth

# 2. 创建registry用户,用于上传和下载镜像
[root@484288 ~]$ htpasswd -Bbn ops 123456. > /etc/docker/auth/registry

[root@484288 ~]$ cat /etc/docker/auth/registry
ops:$2y$05$zvMWW6tjQriMEKNinHyL.uPsvSYfR9iYWROMAvwrA7AfRy1K0SUTC

创建用户还可以使用如下命令:

1
2
3
$ docker run \
--entrypoint htpasswd \
registry:2 -Bbn ops 123456. > /etc/docker/auth/registry

3. 启动容器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 指定443端口
$ docker run -d \
--name registry \
-e REGISTRY_HTTP_ADDR=0.0.0.0:443 \
-p 443:443 \
registry:2

# 或者:默认的5000端口
$ docker run -d \
-p 5000:5000 \
--restart=always \
--name registry \
-v "$(pwd)"/ssl:/ssl \
-e REGISTRY_HTTP_TLS_CERTIFICATE=/ssl/certs/reg.cntv.local.crt \
-e REGISTRY_HTTP_TLS_KEY=/ssl/private/reg.cntv.local.key \
-v /etc/docker/auth:/auth \
-e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
-e REGISTRY_AUTH_HTPASSWD_PATH=/auth/registry \
-e "REGISTRY_AUTH=htpasswd" \
-v /data/registry:/var/lib/registry \
registry:2

api:

1
2
3
4
5
6
7
Path:        "/v2/",
Path: "/v2/{name:" + reference.NameRegexp.String() + "}/tags/list",
Path: "/v2/{name:" + reference.NameRegexp.String() + "}/manifests/{reference:" + reference.TagRegexp.String() + "|" + digest.DigestRegexp.String() + "}",
Path: "/v2/{name:" + reference.NameRegexp.String() + "}/blobs/{digest:" + digest.DigestRegexp.String() + "}",
Path: "/v2/{name:" + reference.NameRegexp.String() + "}/blobs/uploads/",
Path: "/v2/{name:" + reference.NameRegexp.String() + "}/blobs/uploads/{uuid:[a-zA-Z0-9-_.=]+}",
Path: "/v2/_catalog",

查询、删除镜像:

1
2
3
4
5
6
7
8
9
10
11
12
#查询镜像
curl <仓库地址>/v2/_catalog

#查询镜像tag(版本)
curl <仓库地址>/v2/<镜像名>/tags/list

#删除镜像API
curl -I -X DELETE "<仓库地址>/v2/<镜像名>/manifests/<镜像digest_hash>"

#获取镜像digest_hash
curl <仓库地址>/v2/<镜像名>/manifests/<tag> \
--header "Accept: application/vnd.docker.distribution.manifest.v2+json"

客户端

1. 添加 hosts

1
2
3
$ cat /etc/hosts
...
192.168.117.138 reg.cntv.local

2. 修改 daemon.json 并重启 docker

1
2
3
4
5
6
# 将https://reg.cntv.local添加到insecure-registries
$ cat /etc/docker/daemon.json
{
"registry-mirrors": ["https://3417nt4m.mirror.aliyuncs.com"],
"insecure-registries": ["https://reg.cntv.local:5000"]
}

注意:修改 daemon.json 后需要重启 docker.service

3. 登陆

1
docker login -u ops -p 123456. reg.cntv.local:5000

4. 上传&下载

1
2
3
4
5
6
# 上传
$ docker image tag alpine:3.17 reg.cntv.local:5000/alpine:3.17
$ docker push reg.cntv.local:5000/alpine:3.17

# 下载
$ docker pull reg.cntv.local:5000/alpine:3.17

Docker 之分布式仓库 Harbor

harbor 功能介绍:

  • 基于角色的访问控制
  • 镜像复制
  • 图形化用户界面
  • 审计管理
  • 国际化
  • resful api
  • 部署简单

harbor 组成:

harbor 是由很多容器组成实现完整功能

  • proxy:对应启动组件 nginx。它是一个 nginx 反向代理,代理 notary client(镜像认证)、docker client(镜像上传下载等)和浏览器的访问请求(core service)给后端的各服务
  • UI(Core Service):对应启动组件 harbor-ui。底层数据存储使用 mysql 数据库,主要提供了四个子功能:
    • UI:一个 web 管理页面 ui
    • API:Harbor 暴露的 API 服务
    • Auth:用户认证服务,decode 后的 token 中的用户信息在这里进行认证;auth 后端可以接 db、ldap、uaa 三种认证实现
    • Token 服务(上图中未体现):负责根据用户在每个 project 中的 role 来为每一个 docker push/pull 命令发布一个 token,如果从 docker client 发送给 registry 的请求没有带 token,registry 会重定向请求到 token 服务创建 token
  • registry:对应启动组件 registry。负责存储镜像文件,和处理镜像的 pull/push 命令。Harbor 对镜像进行强制的访问控制,Registry 会将客户端的每个 pull、push 请求转发到 token 服务来获取有效的 token
  • admin service:对应启动组件 harbor-adminserver。是系统的配置管理中心附带检查存储用量,ui 和 jobserver 启动时候需要加载 adminserver 的配置
  • job service:对应启动组件 harbor-jobservice。负责镜像复制工作的,他和 registry 通信,从一个 registry pull 镜像然后 push 到另一个 registry,并记录 job_log
  • log collector:对应启动组件 harbor-log。日志汇总组件,通过 docker 的 log-driver 把日志汇总到一起
  • db:对应启动组件 harbor-db,负责存储 project、 user、 role、replication、image_scan、access 等的 metadata 数据

配置 harbor 仓库

两台 harbor 服务器 10.0.0.118 和 10.0.0.119 均按照以下步骤配置:

  1. 安装 docker
  2. 安装 docker compose,docker compose 必须先于 harbor 安装,否则会报错
  3. 下载 Harbor 安装包并解压缩
  4. 编辑配置文件 harbor.yml,旧版本是 harbor.cfg
  5. 运行 harbor 安装脚本
  6. 实现开机自动启动 harbor
  7. 登录 harbor 主机网站
  8. 建立项目
  9. 修改 harbor 配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 1. 安装docker
$ sudo apt-get update
$ sudo apt-get -y install \
apt-transport-https \
ca-certificates \
curl \
gnupg-agent \
software-properties-common
$ curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo apt-key add -
$ sudo add-apt-repository \
"deb [arch=amd64] https://mirrors.aliyun.com/docker-ce/linux/ubuntu \
$(lsb_release -cs) \
stable"
$ sudo apt-get update
$ sudo apt -y install docker-ce

# 以上是直接安装最新版本,推荐安装时指定版本
$ apt-cache madison docker-ce
$ sudo apt -y install docker-ce=<VERSION_STRING>
1
2
3
4
5
# 2. 安装docker compose
root@u1:~# apt -y install python3-pip # 为了方便,直接安装pip
root@u1:~# pip3 install docker-compose
root@u1:~# docker-compose --version
docker-compose version 1.27.4, build unknown
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 3. 下载Harbor安装包并解压缩
# 可以下载在线安装包或者完整的离线安装包,推荐下载离线包
# 下载网址:https://github.com/goharbor/harbor/releases
# 推荐使用迅雷下载:harbor-offline-installer-v1.10.6.tgz
root@u1:/usr/local/src# tar zxvf harbor-offline-installer-v1.10.6.tgz
root@u1:/usr/local/src# cd harbor/
root@u1:/usr/local/src/harbor# ll
total 700264
drwxr-xr-x 2 root root 4096 Nov 24 13:29 ./
drwxr-xr-x 3 root root 4096 Nov 24 13:29 ../
-rw-r--r-- 1 root root 3398 Nov 17 03:58 common.sh
-rw-r--r-- 1 root root 717021676 Nov 17 03:59 harbor.v1.10.6.tar.gz
-rw-r--r-- 1 root root 5882 Nov 17 03:58 harbor.yml
-rwxr-xr-x 1 root root 2284 Nov 17 03:58 install.sh*
-rw-r--r-- 1 root root 11347 Nov 17 03:58 LICENSE
-rwxr-xr-x 1 root root 1749 Nov 17 03:58 prepare*
1
2
3
4
5
6
7
8
9
10
11
12
13
# 4. 编辑配置文件 harbor.yml
root@u1:/usr/local/src/harbor# vim harbor.yml
#只需要修改下面两行
hostname: 10.0.0.118 # 修改此行,指向当前主机IP 或 FQDN
harbor_admin_password: 123456 # 修改此行指定harbor登录用户admin的密码

# 不需要https,注释以下内容
# https:
# # https port for harbor, default is 443
# port: 443
# # The path of cert and key files for nginx
# certificate: /your/certificate/path
# private_key: /your/private/key/path
1
2
3
4
5
6
7
# 5. 运行 harbor 安装脚本
root@u1:/usr/local/src# mv harbor /usr/local/
root@u1:/usr/local/src# cd /usr/local/harbor/
root@u1:/usr/local/src# ./prepare
...
root@u1:/usr/local/src# ./install.sh
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 6. 实现开机自动启动 harbor
root@u1:~# vim /lib/systemd/system/harbor.service
[Unit]
Description=Harbor
After=docker.service systemd-networkd.service systemd-resolved.service
Requires=docker.service
Documentation=http://github.com/vmware/harbor

[Service]
Type=simple
Restart=on-failure
RestartSec=5
ExecStart=/usr/local/bin/docker-compose -f usr/local/harbor/docker-compose.yml up
ExecStop=/usr/local/bin/docker-compose -f usr/local/harbor/docker-compose.yml down

[Install]
WantedBy=multi-user.target

root@u1:~# systemctl daemon-reload
root@u1:~# systemctl enable --now harbor.service
Created symlink /etc/systemd/system/multi-user.target.wants/harbor.service → /lib/systemd/system/harbor.service.
  1. 登录 harbor 主机网站 http://10.0.0.118

1
2
3
4
5
6
# 9. 修改harbor配置
# 后期如果需要修改harbor配置
1.修改 harbor.yml 文件
2.停止harbor: systemctl stop harbor.service
3.执行prepare
4.启动harbor:sysytemcrl start harbor.service

配置 docker 客户端

客户端 10.0.0.117

  1. 安装 docker
  2. 登录 harbor
  3. 给本地镜像大标签并上传到 harbor
  4. 下载 harbor 的镜像
1
2
# 1. 安装docker
略...
  1. 建立项目

项目可以设置为“公开”,这样不用登陆就可以 pull,但是要想 push 还是得登陆

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 3. 登录harbor.两种方法,命令行或者配置文件
# 方法一:命令行
root@u3:~# vim /usr/lib/systemd/system/docker.service
...
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock --insecure-registry 10.0.0.118 --insecure-registry 10.0.0.119
...
# 方法二:配置文件,推荐
root@u3:~# vim /etc/docker/daemon.json
{
"registry-mirrors": ["https://3417nt4m.mirror.aliyuncs.com"],
"insecure-registries": ["10.0.0.118", "10.0.0.119"]
}
root@u3:~# systemctl restart docker.service
root@u3:~# docker login 10.0.0.118
Username: admin
Password:
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded
1
2
3
4
5
# 3. 给本地镜像大标签并上传到harbor
# 修改 images 的名称,不修改成指定格式无法将镜像上传到 harbor 仓库
# 格式:Harbor主机IP/项目名/image名字:版本
root@u3:~# docker tag alpine:latest 10.0.0.118/library/alpine-base:3.12
root@u3:~# docker push 10.0.0.118/library/alpine-base:3.12
1
2
3
# 4. 下载harbor的镜像
# 另开一台机器,安装docker,daemon.json设置insecure-registries,就可以直接下载镜像
root@u4:~# docker image pull 10.0.0.118/library/alpine-base:3.12

harbor 高可用

实现 harbor 有两种方式:共享存储 和 镜像复制,这里只学习后者,可以通过 web 界面直接配置镜像复制

前面配置了两台 harbor 仓库,10.0.0.118 和 10.0.0.119,但是只使用了一台,这里使用另一台组成双向复制

注意:不是主从,而是双向复制,通过负载均衡器将请求分流

h1:10.0.0.118
h2:10.0.0.119

  1. 确保 h1 和 h2 中有相同的项目

  2. h1 和 h2 均新建仓库,目标分别为对方

    复制模式选择 push-based

  3. h1 和 h2 均新建复制,目标分别为对方

  4. 测试,分别在 h1 和 h2 上传(注意上传前先登录 harbor)然后删除,都能同步成功

harbor 配置 https

harbor 默认使用 http,可以配置 https,不过我认为没有这个必要

在前面配置好 harbor 仓库的基础上,执行以下操作:

  1. 配置服务端

    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
    root@u1:~# mkdir -p /root/.rnd
    root@u1:~# mkdir -p /usr/local/harbor/certs
    root@u1:~# cd /usr/local/harbor/certs
    root@u1:/usr/local/harbor/certs# ls
    # 创建私有CA
    root@u1:/usr/local/harbor/certs# openssl req -newkey rsa:1024 -nodes -sha256 -keyout ca.key -x509 -subj "/CN=ca.ljk.org" -days 3650 -out ca.crt
    Generating a RSA private key
    ..............+++++
    ...................................+++++
    writing new private key to 'ca.key'
    -----
    root@u1:/usr/local/harbor/certs# ls
    ca.crt ca.key
    # 生成harbor主机的证书申请
    root@u1:/usr/local/harbor/certs# openssl req -newkey rsa:1024 -nodes -sha256 -subj "/CN=harbor.ljk.org" -keyout harbor.ljk.org.key -out harbor.ljk.org.csr
    Generating a RSA private key
    .....................................................................+++++
    ..........+++++
    writing new private key to 'harbor.ljk.org.key'
    -----
    root@u1:/usr/local/harbor/certs# ls
    ca.crt ca.key harbor.ljk.org.csr harbor.ljk.org.key
    # 给harbor主机颁发证书
    root@u1:/usr/local/harbor/certs# openssl x509 -req -in harbor.ljk.org.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out harbor.ljk.org.crt
    Signature ok
    subject=CN = harbor.ljk.org
    Getting CA Private Key
    root@u1:/usr/local/harbor/certs# tree
    .
    ├── ca.crt
    ├── ca.key
    ├── ca.srl
    ├── harbor.ljk.org.crt
    ├── harbor.ljk.org.csr
    └── harbor.ljk.org.key

    0 directories, 6 files
    root@u1:/usr/local/harbor/certs# vim ../harbor.yml
    hostname: harbor.ljk.org # 此行需改为域名
    https:
    # https port for harbor, default is 443
    port: 443
    # The path of cert and key files for nginx
    certificate: /usr/local/harbor/certs/harbor.ljk.org.crt
    private_key: /usr/local/harbor/certs/harbor.ljk.org.key
    ...
    root@u1:/usr/local/harbor/certs# cd ..
    root@u1:/usr/local/harbor# systemctl stop harbor.service # 这一步要等挺久
    root@u1:/usr/local/harbor# ./prepare
    root@u1:/usr/local/harbor# ./install.sh
    root@u1:/usr/local/harbor# systemctl start harbor.service

    访问:https://10.0.0.118

  2. 配置客户端

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    root@u3:~# echo '10.0.0.118 harbor.ljk.org' >> /etc/hosts

    # 因为是自建证书,所以harbor规定,客户端也下载一份,位于/etc/docker/certs.d/harbor.ljk.org
    # 其实,只需要harbor.ljk.org.crt即可,这里为了方便,全部拷贝过来
    root@u3:~# scp lujinkai@harbor.ljk.org:/usr/local/harbor/certs/* /etc/docker/certs.d/harbor.ljk.org/

    # 登录成功
    root@u3:~# docker login harbor.ljk.org

    # 上传镜像
    root@u3:~# docker image push harbor.ljk.org/library/alpine:v1

docker 网络支持 5 种网络模式:none、bridge、host、container、network-net

创建新容器时,docker run命令可以指定网络模式:

1
2
3
4
5
6
7
8
docker run --network <mode>

# mode:默认是bridge
none
bridge
host
container:容器名或容器ID
自定义网络名称

bridge 模式

默认模式,容器连接到一个虚拟网桥与外界通信,通过 SNAT 访问外网,通过 DNAT 可以让容器被外部主机访问,所以此模式也称为 NAT 模式

此模式会启动宿主机的 ip_forward 功能

bridge 模式特点:

  • 网络资源隔离:不同主机的容器之间无法直接通信
  • 因为 NAT 转换,所以性能较低
  • 端口管理繁琐,每个容器必须手动指定唯一的端口,容易产生端口冲突

修改默认的 bridge 模式网络配置:

方法一:修改 docker.service

1
2
3
4
5
# docker0默认ip为172.17.0.1/16,修改为10.100.0.1/24
root@Z510:~# vim /usr/lib/systemd/system/docker.service
...
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock --bip=10.100.0.1/24
...

方法二:修改 daemon.json,推荐这种方法

1
2
3
4
5
6
7
8
9
10
root@Z510:~# vim /etc/docker/daemon.json
# hosts 指定docker守护进程侦听的地址,默认/var/run/docker.sock
"hosts": ["tcp://0.0.0.0:2375", "fd://"],
"bip": "192.168.100.100/24", # docker0网卡的IP
"fixed-cidr": "192.168.100.128/26", # 分配给容器的IP范围
"fixed-cidr-v6": "2001:db8::/64",
"mtu": 1500,
"default-gateway": "192.168.100.200", # 网关必须和bip在同一个网段
"default-gateway-v6": "2001:db8:abcd::89",
"dns": [ "1.1.1.1", "8.8.8.8"]

host 模式

容器和宿主机之间网络不隔离(其他资源隔离),容器不创建自己的虚拟网卡,而是使用宿主机的网卡和 IP 地址,因此在容器里面查看到的 IP 信息就是宿主机的信息,访问容器的时候直接使用宿主机 IP+容器端口即可

此模式由于直接使用宿主机的网络无需转换,网络性能最高,但是各容器内使用的端口不能相同,适用于运行容器端口比较固定的业务

host 网络模式特点:

  • 共享宿主机网络
  • 网络性能无损耗
  • 网络故障排除相对简单
  • 各容器网络无隔离
  • 网络资源无法分别统计
  • 端口管理困难: 容易产生端口冲突
  • 不支持端口映射

范例:

1
docker run -d --network host --name web1 nginx-centos7-base:1.6.1

none 模式

容器不会进行任何网络配置,没有网卡、没有 IP 也没有路由,因此默认无法与外界通信,需要手动添加网卡配置 IP 等,所以极少使用

containter 模式

指定一个已经存在的容器(选择与其通信频繁的容器),共享其网络,因此这个容器的端口不能和被指定容器的端口冲突

这种模式也较少使用

自定义网络模式

1
2
3
4
5
6
7
8
9
10
11
# docker network --help
Usage: docker network COMMAND

Commands:
connect Connect a container to a network
create Create a network
disconnect Disconnect a container from a network
inspect Display detailed information on one or more networks
ls List networks
prune Remove all unused networks
rm Remove one or more networks

自定义网络内的容器可以直接通过容器进行相互的访问

可以使用自定义网络模式,实现不同集群应用的独立网络管理,而互补影响,而且在一个网络内,可以直接利用容器名称相互访问,非常便利

创建自定义网络:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# docker network create --help
Usage: docker network create [OPTIONS] NETWORK

Options:
--attachable Enable manual container attachment
--aux-address map Auxiliary IPv4 or IPv6 addresses used by Network driver (default map[])
--config-from string The network from which copying the configuration
--config-only Create a configuration only network
-d, --driver string Driver to manage the Network (default "bridge")
--gateway strings IPv4 or IPv6 Gateway for the master subnet
--ingress Create swarm routing-mesh network
--internal Restrict external access to the network
--ip-range strings Allocate container ip from a sub-range
--ipam-driver string IP Address Management Driver (default "default")
--ipam-opt map Set IPAM driver specific options (default map[])
--ipv6 Enable IPv6 networking
--label list Set metadata on a network
-o, --opt map Set driver specific options (default map[])
--scope string Control the network's scope
--subnet strings Subnet in CIDR format that represents a network segment
1
2
3
docker network create -d <mode> --subnet <CIDR> --gateway <网关> <自定义网络名称>

#注意mode不支持host和none

容器间通信

禁止同宿主机的不同容器间通信

docker.service

1
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock --icc=false

daemon.json

1
"icc": false

容器名称互联

前文中,自定义网络模式,可以实现容器名称互联,如果是默认模式,想要实现这个功能,需要在创建容器时,使用–link 选项

范例:实现 a1 和 a2 容器名称互联

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 1. 创建容器a1
root@Z510:~# docker container run -it --name a1 alpine:latest
/ # hostname -i
172.17.0.2
# 2. 创建容器a2
root@Z510:~# docker container run -it --name a2 --link a1 alpine:latest
/ # hostname -i
172.17.0.3
/ # cat /etc/hosts
...
172.17.0.2 a1 6079167b0ed1
172.17.0.3 b4e8abef6893
/ # ping a1 # a2可以ping通a1
PING a1 (172.17.0.2): 56 data bytes
64 bytes from 172.17.0.2: seq=0 ttl=64 time=0.191 ms
...
# 3. 修改容器a1的hosts
/ # vi /etc/hosts
# 添加如下行
172.17.0.3 a2 b4e8abef6893
/ # ping a2 # a1可以ping通a2
PING a2 (172.17.0.3): 56 data bytes
64 bytes from 172.17.0.3: seq=0 ttl=64 time=0.135 ms
...

同宿主机之间不同网络的容器通信

同一个宿主机,创建两个容器,一个使用自定义网络,一个使用默认的 bridge 模式,默认这两个容器是无法通信的,如果想要实现通信,有两种方式:修改 iptables 规则 和 通过docker network connect实现

本笔记只记录第二种方式:

1
2
3
4
5
6
7
8
9
10
# docker network connect --help
Usage: docker network connect [OPTIONS] NETWORK CONTAINER

Options:
--alias strings Add network-scoped alias for the container
--driver-opt strings driver options for the network
--ip string IPv4 address (e.g., 172.30.100.104)
--ip6 string IPv6 address (e.g., 2001:db8::33)
--link list Add link to another container
--link-local-ip strings Add a link-local address for the container

范例:默认 docker0 中的容器为 test1,自定义网络 test-net 中的容器为 test2

1
2
3
4
5
6
7
# 1. 创建网络和容器,过程略...
# 2. 让默认网络中容器test1可以连通自定义网络test-net的容器test2
docker network connect test-net test1
ip a # 可以看到新增一个网卡,ip和test-net同网段
# 3. 让自定义网络中容器test2可以连通默认网络的容器test1
docker network connect bridge test2
ip a # 可以看到新增一个网卡,ip和docker0同网段

docker 容器创建后,必不可少的要和其它主机或容器进行网络通信

docker 的网络模式和 KVM 很相似

docker 网络连接模式

跨宿主机的容器之间网络通信

四种方法:

  1. 桥接
  2. NAT
  3. Open vSwitch
  4. weave

生产中,以上四种都不用,而是通过 k8s 实现,如果真的用到了,再回来复习课件

案例:docker & LVS 实现网络架构高可用

整体规划图:

过程略..

docker 镜像由多个只读层叠加而成,启动容器时,docker 会加载只读镜像层并在镜像栈顶部添加一个读写层

所有的修改都被复制到读写层,保存在 diff 目录,原理就是“写时复制(COW)”

但是一旦删除容器,读写层也会被删除,所以为了数据持久化,需要将容器中的数据保存到宿主机的制定目录,方法就是使用数据卷(Data Volume):直接将宿主机目录挂载到容器的指定目录

数据卷

数据卷实际上就是宿主机上的目录或文件,可以直接 mount 到容器中使用

实际生产环境中,需要针对不同类型的服务,不同类型的数据存储要求做到相应的规划,最终保证服务的可扩展性、稳定性以及数据的安全性

使用场景:数据库、日志、静态 web 页面、应用配置文件、多容器间目录或文件共享

数据卷特点:

  • 可以多容器之间共享
  • 依赖宿主机目录,宿主机出问题,依赖的容器就会受影响,当宿主机较多时,不方便统一管理
  • 镜像中的挂载点中包含数据,则在容器初始化时会将数据拷贝到数据卷(匿名和命名)中

数据卷的使用

1
2
3
4
5
docker container run -v [host-src:]container-dest[:<options>]

options:
ro:容器内对此数据卷只读,代码等文件适合设置为只读
rw:容器内对此数据卷读写,默认
1
2
3
4
5
6
7
8
# 将宿主机目录挂载容器目录,两个目录都可自动创建
-v <宿主机绝对路径的目录或文件>:<容器目录或文件>[:ro]

# 匿名卷,宿主机自动生成/var/lib/docker/volumes/<卷ID>/_data目录,并挂载至容器指定路径
-v <容器内路径>

# 命名卷,宿主机自动生成/var/lib/docker/volumes/<卷名>/_data目录,并挂载至容器指定路径
-v <卷名>:<容器目录路径>

管理卷命令:

1
2
3
4
5
6
7
8
9
# docker volume --help
Usage: docker volume COMMAND

Commands:
create Create a volume
inspect Display detailed information on one or more volumes
ls List volumes
prune Remove all unused local volumes
rm Remove one or more volumes

关于匿名卷和命名卷:

命名卷,因为有名字可以指定,在用过一次后,以后挂载容器的时候还可以使用,所以一般需要保存的数据使用命名卷保存。

匿名卷没有名字,随容器建立而建立,当容器消亡,匿名卷即使还存在,但也失去了意义,因此匿名卷只存放无关紧要的临时数据。

Dockerfile 中指定 VOLUME 为匿名数据卷,其目的只是为了将某个路径确定为卷。

数据卷默认可能会保存于 /var/lib/docker/volumes,不过一般不需要、也不应该访问这个位置。

按照最佳实践的要求,不应该在容器存储层内进行数据写入操作,所有写入应该使用卷。

1
2
3
4
# 查看数据卷的挂载关系
docker inspect --format="{{.Mounts}}" <容器ID>
# 删除所有数据卷
docker volume rm `docker volume ls -q`

案例

MySQL 使用数据卷:

1
2
3
4
5
docker run -d \
--name mysql \
-p 3306:3306 \
-v /data/mysql/:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 mysql:5.7.30

文件数据卷:

1
2
3
4
5
docker run -d \
-v /data/bin/catalina.sh:/apps/tomcat/bin/catalina.sh:ro \
-v /data/testapp:/data/tomcat/webapps/testapp \
-v /data/logs:/apps/tomcat/logs \
-p 8080:8080 tomcat-web:app1