Nginx状态统计模块

前面简单介绍了Nginx的手动编译安装过程,详细过程请参见Nginx服务搭建;
而Nginx内置了状态统计模块,用来反馈当前的web访问情况,那么该如何开启Nginx内置状态统计模块呢?且我们该如何通过客户端进入状态统计页面?

编译安装Nginx

cd /opt/nginx-1.6.0/       //这里我将Nginx源码包解压在/opt目录下

 ./configure 
--prefix=/usr/local/nginx 
--user=nginx 
--group=nginx 
--with-http_stub_status_module    //开启stub_status状态统计模块,切记要将状态统计模块编译
make && make install 
/usr/local/nginx/sbin/nginx -V //查看Nginx是否安装状态统计模块成功

修改配置文件

要使用Nginx的状态统计功能,除了编译模块以外,还需要修改Nginx的主配置文件制定访问位置并添加stub_status配置代码。

vim /usr/local/nginx/conf/nginx.conf
server {
        listen       80;
        server_name  localhost;
        charset utf-8;

        location / {
            root   html;
            index  index.html index.htm;
        }

        location ~ /status {     //访问位置/status
        stub_status   on;        //开启状态统计功能
        access_log off;          //关闭此模块的日志
        }                       //在"server"这里插入的这4行

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

       }
    }

重启Nginx服务即可,详细服务启动配置脚本请参见:
Nginx服务搭建。

查看状态统计页面

由于本机采用内网IP形式提供服务,且IP地址为192.168.100.111,故在客户端访问:192.168.100.111/status即可进入状态统计页面。为了试验效果,每次刷新页面即可看到状态统计页面的情况:

未分类

未分类

未分类

Debian Linux安装NFS

要在两台Linux电脑上共享一个目录,最简单的方式是使用NFS,将一台电脑的目录映射到另一台。

在Debian上实现NFS非常容易,首先,在作为服务端的电脑192.168.1.99上安装:

# apt-get install nfs-common nfs-kernel-server

创建需要共享的目录:

# mkdir /srv/upload
# chmod a+w /srv/upload

然后修改/etc/exports文件,将需要共享的目录和客户端添加进来:

/srv/upload  192.168.1.100(rw,sync)

表示允许IP为192.168.1.100的客户端以rw的模式访问。如果以只读模式访问则设置为ro。

然后启动服务:

# /etc/init.d/nfs-kernel-server start

在客户端安装NFS:

# apt-get install nfs-common

创建目录并挂载NFS:

# mkdir /mnt/upload
# mount 192.168.1.99:/srv/upload /mnt/upload

此时已经可以写入/mnt/upload,对应的服务器端可以看到创建的文件。

要在客户端每次启动时自动挂载NFS,可以编辑/etc/fstab,添加一行:

192.168.1.99:/srv/upload /mnt/upload nfs rsize=8192,wsize=8192,timeo=14,intr

均为缺省参数。

MySQL 字符串 转 int/double CAST与CONVERT 函数的用法

MySQL 的CAST()和CONVERT()函数可用来获取一个类型的值,并产生另一个类型的值。两者具体的语法如下:

CAST(value as type);  
CONVERT(value, type);  

就是CAST(xxx AS 类型), CONVERT(xxx,类型)。

mysql> SELECT CAST('3.35' AS signed);  
+------------------------+  
| CAST('3.35' AS signed) |  
+------------------------+  
|                      3 |  
+------------------------+  
1 row in set  

可以转换的类型是有限制的。这个类型可以是以下值其中的一个:

  • 二进制,同带binary前缀的效果 : BINARY
  • 字符型,可带参数 : CHAR()
  • 日期 : DATE
  • 时间: TIME
  • 日期时间型 : DATETIME
  • 浮点数 : DECIMAL
  • 整数 : SIGNED
  • 无符号整数 : UNSIGNED

下面举几个例子:

例一

mysql> SELECT CONVERT('23',SIGNED);  
+----------------------+  
| CONVERT('23',SIGNED) |  
+----------------------+  
|                   23 |  
+----------------------+  
1 row in set  

例二

mysql> SELECT CAST('125e342.83' AS signed);  
+------------------------------+  
| CAST('125e342.83' AS signed) |  
+------------------------------+  
|                          125 |  
+------------------------------+  
1 row in set  

例三

mysql> SELECT CAST('3.35' AS signed);  
+------------------------+  
| CAST('3.35' AS signed) |  
+------------------------+  
|                      3 |  
+------------------------+  
1 row in set  

像上面例子一样,将varchar 转为int 用 cast(a as signed),其中a为varchar类型的字符串。

mariadb数据库冷备份实验

数据的重要性不言而喻,做好数据备份方能在数据损坏丢失时及时恢复

  • 冷备份
  • 热备份
  • 完全备份
  • 增量备份

冷备份即暂停Mariadb服务,用户不能读写访问数据库,直接拷贝整个数据文件

实验拓扑

未分类

实验准备

  • 两台CentOS 7.4 server
  • mariadb软件版本 mariadb-server-5.5.56-2.el7.x86_64
  • sql数据库脚本,一个用于生成测试数据库hellodb,一个是存储过程,批量增加10W条数据记录
    https://github.com/zhongchengling/linuxshell

实验步骤

172.16.8.71–DB1

yum install -y mariadb-server

修改/etc/my.cnf文件拆分innodb引擎的数据库文件

innodb_file_per_table = ON
systemctl start mariadb.service
git clone [email protected]:zhongchengling/linuxshell.git
cd linuxshell
mysql < hellodb_innodb.sql
mysql --database=hellodb < testlog.sql
  • 安装MariaDB软件,修改/etc/my.cnf配置文件,并启动服务
  • 从同性交友网站GitHub上克隆sql脚本,导入数据库hellodb和存储引擎
  • 调用call pro_testlog();导入十万条记录
MariaDB [hellodb]> show databases;
MariaDB [hellodb]> show tables;
MariaDB [hellodb]> call pro_testlog();
MariaDB [hellodb]> select count(*) from testlog;

未分类

systemctl stop mariadb.service

暂停数据服务,打包备份/var/lib/mysql下的所有数据文件到172.16.8.7

cd /var/lib/
tar -Jcvf data.tar.xz mysql
scp /etc/my.cnf [email protected]:/etc
scp data.tar.xz [email protected]:/root

数据备份完成,在172.16.8.71机器上进行数据的验证恢复

172.16.8.71–DB1

  • 同样地yum安装Mariadb,确保版本相同,以避免潜在问题
  • 解压数据文件到/var/lib/mysql
tar -xvf data.tar.xz -C /var/lib/mysql
  • 启动mairadb服务,验证数据

未分类

LVM的创建、扩容和缩减

一、创建LVM系统

1. 硬盘分区

首先我们需要为我们的虚拟机添加一个虚拟硬盘,过程略。

查看硬盘设备:

[root@localhost ~]# fdisk -l
...
Disk /dev/sdc: 10.7 GB, 10737418240 bytes
255 heads, 63 sectors/track, 1305 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0xa2c6e480

   Device Boot      Start         End      Blocks   Id  System
...

如上/dev/sdc是我们新添加的硬盘设备。
我们需要在/dev/sdc基础上创建分区并修改分区系统类型为8e:

[root@localhost ~]# fdisk /dev/sdc 

WARNING: DOS-compatible mode is deprecated. It is strongly recommended to
         switch off the mode (command 'c') and change display units to
         sectors (command 'u').

Command (m for help): n                                   # 创建新分区
Command action
   e   extended                                                    # 扩展分区
   p   primary partition (1-4)                                # 主分区
p
Partition number (1-4): 1                                   # 分区号
First cylinder (1-1305, default 1):                     # 默认从第一个柱面开始划分
Using default value 1
Last cylinder, +cylinders or +size{K,M,G} (1-1305, default 1305): +5G   # 分区大小

Command (m for help): n
Command action
   e   extended
   p   primary partition (1-4)
p
Partition number (1-4): 2
First cylinder (655-1305, default 655): 
Using default value 655
Last cylinder, +cylinders or +size{K,M,G} (655-1305, default 1305): +3G

Command (m for help): n
Command action
   e   extended
   p   primary partition (1-4)
p
Partition number (1-4): 3
First cylinder (1048-1305, default 1048): 
Using default value 1048
Last cylinder, +cylinders or +size{K,M,G} (1048-1305, default 1305): 
Using default value 1305

Command (m for help): t                                    # 修改分区系统类型
Partition number (1-4): 1                                    # 选择要修改的分区号
Hex code (type L to list codes): 8e                    #  修改为LVM分区类型,可使用'L'查看所有可使用类型
Changed system type of partition 1 to 8e (Linux LVM)

Command (m for help): t
Partition number (1-4): 2
Hex code (type L to list codes): 8e
Changed system type of partition 2 to 8e (Linux LVM)

Command (m for help): t
Partition number (1-4): 3
Hex code (type L to list codes): 8e
Changed system type of partition 3 to 8e (Linux LVM)

Command (m for help): p                                   # 查看分区情况

Disk /dev/sdc: 10.7 GB, 10737418240 bytes
255 heads, 63 sectors/track, 1305 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0xa2c6e480

   Device Boot      Start         End      Blocks   Id  System
/dev/sdc1               1         654     5253223+  8e  Linux LVM
/dev/sdc2             655        1047     3156772+  8e  Linux LVM
/dev/sdc3            1048        1305     2072385   8e  Linux LVM

Command (m for help): w                                   # 保存分区表并退出
The partition table has been altered!

Calling ioctl() to re-read partition table.
Syncing disks.
[root@localhost ~]# 

2. 创建物理卷

创建物理卷的命令为pvcreate,利用该命令将希望添加到卷组的所有分区或者硬盘创建为物理卷。

[root@localhost ~]# pvcreate /dev/sdc{1,2}             # 将/dev/sdc1和2创建为物理卷
  Physical volume "/dev/sdc1" successfully created
  Physical volume "/dev/sdc2" successfully created
[root@localhost ~]# pvs                                           # 查看物理卷信息
  PV         VG     Fmt  Attr PSize  PFree   
  /dev/sdc1         lvm2 ----  5.01g    5.01g
  /dev/sdc2         lvm2 ----  3.01g    3.01g
[root@localhost ~]# pvdisplay                                 # 查看物理卷详细信息
 ...
  "/dev/sdc1" is a new physical volume of "5.01 GiB"
  --- NEW Physical volume ---
  PV Name               /dev/sdc1
  VG Name               
  PV Size               5.01 GiB
  Allocatable           NO
  PE Size               0   
  Total PE              0
  Free PE               0
  Allocated PE          0
  PV UUID               jKHeSG-XYna-zFYi-9Kd9-bWRM-f1zY-Qs7FZZ

  "/dev/sdc2" is a new physical volume of "3.01 GiB"
  --- NEW Physical volume ---
  PV Name               /dev/sdc2
  VG Name               
  PV Size               3.01 GiB
  Allocatable           NO
  PE Size               0   
  Total PE              0
  Free PE               0
  Allocated PE          0
  PV UUID               WcKTKi-av0e-so5s-QVXB-yf7G-Tynz-9TQDJb

[root@localhost ~]# 

3. 创建卷组

创建卷组的命令为vgcreate,将使用pvcreate建立的物理卷创建为一个完整的卷组。

[root@localhost ~]# vgcreate myvg /dev/sdc{1,2}     # 创建卷组
  Volume group "myvg" successfully created
[root@localhost ~]# vgs                                            # 查看卷组信息
  VG     #PV #LV #SN Attr   VSize  VFree   
  myvg     2   0   0 wz--n-  8.02g    8.02g
[root@localhost ~]# vgdisplay myvg                         # 查看卷组详细信息
  --- Volume group ---
  VG Name               myvg
  System ID             
  Format                lvm2
  Metadata Areas        2
  Metadata Sequence No  1
  VG Access             read/write
  VG Status             resizable
  MAX LV                0
  Cur LV                0
  Open LV               0
  Max PV                0
  Cur PV                2
  Act PV                2
  VG Size               8.02 GiB                                     # 卷组的大小,由上面两个分区决定
  PE Size               4.00 MiB                                     # PE大小可以在vgcreate命令中使用'-s'指定,默认为4M
  Total PE              2052
  Alloc PE / Size       0 / 0   
  Free  PE / Size       2052 / 8.02 GiB
  VG UUID               4aIItM-FyPx-GJ47-1A70-b8W4-EwfH-beFZrZ

[root@localhost ~]# pvdisplay                                   # 再次查看物理卷,跟上一次查看有所区别
  --- Physical volume ---
  PV Name               /dev/sdc1
  VG Name               myvg
  PV Size               5.01 GiB / not usable 2.10 MiB
  Allocatable           yes 
  PE Size               4.00 MiB
  Total PE              1282
  Free PE               1282
  Allocated PE          0
  PV UUID               jKHeSG-XYna-zFYi-9Kd9-bWRM-f1zY-Qs7FZZ

  --- Physical volume ---
  PV Name               /dev/sdc2
  VG Name               myvg
  PV Size               3.01 GiB / not usable 2.79 MiB
  Allocatable           yes 
  PE Size               4.00 MiB
  Total PE              770
  Free PE               770
  Allocated PE          0
  PV UUID               WcKTKi-av0e-so5s-QVXB-yf7G-Tynz-9TQDJb

[root@localhost ~]# 

4. 创建逻辑卷

创建逻辑卷的命令为lvcreate

[root@localhost ~]# lvcreate -L 6G -n mylv myvg
  Logical volume "mylv" created.
[root@localhost ~]#

使用方法:

lvcreate -L #G -n LV_NAME VG_NAME

参数解释:

  • -L: 指定逻辑卷大小,不可超过卷组大小
  • -n: 指定逻辑卷名称

5. 挂载文件系统

至此我们的逻辑卷就已创建完成,接下来我们就需要格式化分区并挂载使用啦。

[root@localhost ~]# mkfs.ext4 /dev/myvg/mylv      # 格式化分区
mke2fs 1.41.12 (17-May-2010)
Filesystem label=
OS type: Linux
Block size=4096 (log=2)
Fragment size=4096 (log=2)
Stride=0 blocks, Stripe width=0 blocks
393216 inodes, 1572864 blocks
78643 blocks (5.00%) reserved for the super user
First data block=0
Maximum filesystem blocks=1610612736
48 block groups
32768 blocks per group, 32768 fragments per group
8192 inodes per group
Superblock backups stored on blocks: 
    32768, 98304, 163840, 229376, 294912, 819200, 884736

Writing inode tables: done                            
Creating journal (32768 blocks): done
Writing superblocks and filesystem accounting information: done

This filesystem will be automatically checked every 36 mounts or
180 days, whichever comes first.  Use tune2fs -c or -i to override.
[root@localhost ~]# mount /dev/myvg/mylv /mnt/      # 挂载分区到/mnt目录下
[root@localhost ~]# df -h
Filesystem            Size  Used Avail Use% Mounted on
...
/dev/mapper/myvg-mylv
                      5.8G   12M  5.5G   1% /mnt
[root@localhost ~]# 

二、扩展逻辑卷

上一章我们介绍了如何创建LVM系统,并挂载使用。如果我们分配的空间不够用,那么就需要我们来扩展分区大小了。
接下来我们将/dev/sdc3分区也加入到卷组,并扩展逻辑卷空间。

[root@localhost ~]# pvcreate /dev/sdc3                 # 创建物理卷
  Physical volume "/dev/sdc3" successfully created
[root@localhost ~]# vgextend myvg /dev/sdc3      # 添加物理卷到卷组
  Volume group "myvg" successfully extended
[root@localhost ~]# pvs                                         # 查看物理卷信息
  PV         VG     Fmt  Attr PSize  PFree   
  /dev/sdb   datavg lvm2 a--u 20.00g 1020.00m
  /dev/sdc1  myvg   lvm2 a--u  5.01g       0 
  /dev/sdc2  myvg   lvm2 a--u  3.01g    2.02g
  /dev/sdc3  myvg   lvm2 a--u  1.97g    1.97g        # /dev/sdc3已加入myvg卷组
[root@localhost ~]# vgs                                        # 卷组信息
  VG     #PV #LV #SN Attr   VSize  VFree   
  datavg   1   1   0 wz--n- 20.00g 1020.00m
  myvg     3   1   0 wz--n-  9.99g    3.99g               # 卷组容量比之前扩大
[root@localhost ~]# lvextend -L 9G /dev/myvg/mylv    # 扩展逻辑卷容量到9G
  Size of logical volume myvg/mylv changed from 6.00 GiB (1536 extents) to 9.00 GiB (2304 extents).
  Logical volume mylv successfully resized.
[root@localhost ~]# lvs
  LV     VG     Attr       LSize  Pool Origin Data%  Meta%  Move Log Cpy%Sync Convert
  nsas01 datavg -wi-ao---- 19.00g                                                    
  mylv   myvg   -wi-a-----  9.00g                              # 逻辑卷容量成功扩展到9G
[root@localhost ~]# df -h
Filesystem            Size  Used Avail Use% Mounted on
...
/dev/mapper/myvg-mylv
                      5.8G   12M  5.5G   1% /mnt             # 但是分区空间还是初始大小
[root@localhost ~]# resize2fs -p /dev/myvg/mylv  # 改变分区大小为逻辑卷大小
resize2fs 1.41.12 (17-May-2010)
Filesystem at /dev/myvg/mylv is mounted on /mnt; on-line resizing required
old desc_blocks = 1, new_desc_blocks = 1
Performing an on-line resize of /dev/myvg/mylv to 2359296 (4k) blocks.
The filesystem on /dev/myvg/mylv is now 2359296 blocks long.

[root@localhost ~]# df -h
Filesystem            Size  Used Avail Use% Mounted on
...
/dev/mapper/myvg-mylv
                      8.8G   14M  8.3G   1% /mnt           # 分区大小成功扩展到9G
[root@localhost ~]# 

三、缩减逻辑卷

注意事项:

  1. 缩减前需要先卸载挂载;
  2. 要确保缩减后的空间依然能够存储原有的所有数据;
  3. 缩减之前需要先强行检查文件,确保文件系统处于一致性状态。
[root@localhost ~]# umount /dev/myvg/mylv             # 卸载挂载分区
[root@localhost ~]# e2fsck -f /dev/myvg/mylv           # 强行检查文件系统
e2fsck 1.41.12 (17-May-2010)
Pass 1: Checking inodes, blocks, and sizes
Pass 2: Checking directory structure
Pass 3: Checking directory connectivity
Pass 4: Checking reference counts
Pass 5: Checking group summary information
/dev/myvg/mylv: 11/589824 files (0.0% non-contiguous), 73247/2359296 blocks
[root@localhost ~]# resize2fs /dev/myvg/mylv 8G    # 改变分区大小
resize2fs 1.41.12 (17-May-2010)
Resizing the filesystem on /dev/myvg/mylv to 2097152 (4k) blocks.
The filesystem on /dev/myvg/mylv is now 2097152 blocks long.

[root@localhost ~]# lvreduce -L 8G /dev/myvg/mylv  # 改变逻辑卷大小
  WARNING: Reducing active logical volume to 8.00 GiB.
  THIS MAY DESTROY YOUR DATA (filesystem etc.)
Do you really want to reduce myvg/mylv? [y/n]: y
  Size of logical volume myvg/mylv changed from 9.00 GiB (2304 extents) to 8.00 GiB (2048 extents).
  Logical volume mylv successfully resized.
[root@localhost ~]# mount /dev/myvg/mylv /mnt/
[root@localhost ~]# df -h
...
/dev/mapper/myvg-mylv
                      7.8G   14M  7.4G   1% /mnt                 # 分区大小成功缩减为8G
[root@localhost ~]# 

四、从卷组移除物理卷

[root@localhost ~]# lvs
  LV     VG     Attr       LSize  Pool Origin Data%  Meta%  Move Log Cpy%Sync Convert
  nsas01 datavg -wi-ao---- 19.00g                                                    
  mylv   myvg   -wi-ao----  8.00g                                                    
[root@localhost ~]# vgs
  VG     #PV #LV #SN Attr   VSize  VFree   
  datavg   1   1   0 wz--n- 20.00g 1020.00m
  myvg     3   1   0 wz--n-  9.99g    1.99g
[root@localhost ~]# pvs
  PV         VG     Fmt  Attr PSize  PFree   
  /dev/sdc1  myvg   lvm2 a--u  5.01g       0 
  /dev/sdc2  myvg   lvm2 a--u  3.01g   16.00m
  /dev/sdc3  myvg   lvm2 a--u  1.97g    1.97g
[root@localhost ~]# 

我们的目前状态:

  • 挂载分区空间为8G;
  • 逻辑卷(LV)空间大小为8G;
  • 卷组(VG)空间大小为9.99G,剩余1.99G;
  • 物理卷/dev/sdc3空间未使用。

那么我们是否可以将/dev/sdc3从卷组中移除出来呢?

[root@localhost ~]# pvmove /dev/sdc3              # 移动/dev/sdc3中数据到其他物理卷
  No data to move for myvg
[root@localhost ~]# vgreduce myvg /dev/sdc3  # 从myvg卷组中移除/dev/sdc3物理卷
  Removed "/dev/sdc3" from volume group "myvg"
[root@localhost ~]# pvremove /dev/sdc3           # 移除物理卷/dev/sdc3
  Labels on physical volume "/dev/sdc3" successfully wiped
[root@localhost ~]# pvs                                      # /dev/sdc3成功从卷组myvg中移除
  PV         VG     Fmt  Attr PSize  PFree   
  /dev/sdb   datavg lvm2 a--u 20.00g 1020.00m
  /dev/sdc1  myvg   lvm2 a--u  5.01g       0 
  /dev/sdc2  myvg   lvm2 a--u  3.01g   16.00m
[root@localhost ~]# vgs                                      # 卷组空间大小缩减了/dev/sdc3空间大小
  VG     #PV #LV #SN Attr   VSize  VFree   
  datavg   1   1   0 wz--n- 20.00g 1020.00m
  myvg     2   1   0 wz--n-  8.02g   16.00m
[root@localhost ~]# 

五、常用命令

  • 物理卷管理(PV)
pvcreate、pvdisplay、pvmove、pvremove、pvs
  • 卷组管理(VG)
vgcreate、vgdisplay、vgextend、vgreduce、vgremove、vgrename、vgs
  • 逻辑卷管理(LV)
lvcreate、lvdisplay、lvextend、lvreduce、lvremove、lvrename、lvs

KVM虚拟网络

宿主服务器安装完成KVM,首先要设定网络,以便和主机网络,客户机之间的网络通信在libvirt中运行KVM网络有两种方法:NAT和Bridge,默认为NAT

KVM 客户机网络连接有两种方式:

(1)用户模式(NAT):这种方式是默认网络,数据包由NAT方式通过主机的接口进行传送,可以访问外网,但是无法从外网访问虚拟机网络
(2)桥接模式(Bridge):这种模式允许虚拟机像一台独立的主机一样拥有网络,外部的主机可以直接访问到虚拟机内部

一、基于NAT的虚拟网络

KVM安装时默认的网络配置

未分类

默认宿主机会有一个虚拟网卡virbr0,其实是一个虚拟交换机,并绑定一个网卡virbr0-nic

未分类

这时候主机就变成了一个路由器,可以看到路由功能已经打开,并做了SNAT

[root@localhost ~]# cat /proc/sys/net/ipv4/ip_forward
[root@localhost ~]# iptables -t nat -L

未分类

当有虚拟机启动,如果采用的是默认NAT模式,那么virbr0交换机下就会增加接口,可以看到有2个虚拟接口,代表有2个虚拟机接到了这个交换机上

未分类

可以通过virsh命令查看虚拟机的网卡情况

未分类

向虚拟机添加虚拟机网络
可以通过xml文档添加:

<interface type='network'>
      <mac address='52:54:00:aa:18:d1'/>
      <source network='default'/>    //网络模式
      <model type='virtio'/>        //网卡类型
      <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
</interface>

二、基于网桥的虚拟网络

未分类

首先需要创建虚拟交换机,方式很多,可以通过图形化,nmcli命令等方式,下面通过直接该配置文件创建虚拟交换机

(1)在宿主机添加配置文件

未分类

重新启动网络,让虚拟交换机br0激活

未分类

(2)为虚拟机添加网络

如果虚拟机是关机情况下,执行以下命令

virsh attach-interface vm1  bridge  br0 --model virtio --config        // 下次启动生效

如果虚拟机是开机情况下,执行以下命令

virsh attach-interface vm2  bridge  br0 --model virtio --current       // 立即生效

cd /etc/libvirt/qemu
virsh dumpxml vm2 > vm2.xml      //为了让每次开机都生效,新生成一次配置文件替换原来的配置文件

实践kubernetes ingress controller的四个例子

我之前并未使用过标准的Kubernetes ingress,而是自己实现了一个基于nginx的、类似ingress controller的服务入口管理程序nginx-kit。这个程序会部署到Kubernetes集群中,以Pod形式运行。该Pod由两个Container组成,一个Container放置了一个由脚本启动的nginx;另外一个Container中放置的是一个conf generator程序,它监听Kubernetes集群service对象的变更,并根据变更情况动态生成nginx的配置文件。第一个Container中的脚本会监听配置文件目录的变化,并reload配置文件信息实现Kubernetes内部服务对外暴露入口的动态管理。关于这个程序的详情可以参考我之前写的两篇文章:《Kubernetes集群中的Nginx配置热更新方案》和《为Kubernetes集群中服务部署Nginx入口服务》。

近期在使用ingress controller对内部服务入口的暴露进行动态管理,使用后发现我之前实现的nginx kit与ingress controller的实现之一: ingress-nginx简直是异曲同工。只是当时对Kubernetes理解还不够深入,在设计nginx-kit时格局“太小了”,只实现了一个满足内部需求的”ingress controller”,而不是一个通用的、可扩展的ingress controller:(。

好了!言归正传,这篇文章是ingress的入门文章,将通过四个例子来说明一下ingress controller的实现之一: ingress-nginx在不同服务暴露场景下的使用和配置方法。

一. 例子概述与环境准备

我们有四个例子,见下图中的a) ~ d):

未分类

  • 例子a): 单ingress-nginx controller。通过ingress-svc1将内部服务svc1的http服务端口暴露到集群外,通过访问http://svc1.tonybai.com:30090即可访问svc1服务。
  • 例子b):单ingress-nginx controller。通过ingress-svc1将内部服务svc1的http服务端口暴露到集群外,通过访问http://svc1.tonybai.com:30090即可访问svc1服务;通过ingress-svc2将内部服务svc2的https服务端口暴露到集群外,通过访问http://svc2.tonybai.com:30090即可访问svc2服务。
  • 例子c):单ingress-nginx controller。除了暴露svc1和svc2之外,还暴露了集群内部的一个tcp(四层)服务:svc3,通过tcp连接svc3.tonybai.com:30070即可访问svc3服务。
  • 例子d): 多ingress-nginx controllers。其中nginx-ingress-controller-ic1负责暴露svc1、svc2和svc3服务(访问方式如上面所描述的);nginx-ingress-controller-ic2负责暴露svc4、svc5和svc6,其中svc4是一个http服务;svc5是https服务,svc6是一个tcp(四层)服务。
    这里我们使用一个Kubernetes 1.10.3的集群来循序渐进地实践一下这四个例子。关于这四个例子的源码、chart包以及ingress controllers的yaml源文件在这里可以下载到:
$tree -L 2 ingress-controller-demo
ingress-controller-demo
├── charts
│   ├── svc1
│   ├── svc2
│   ├── svc3
│   ├── svc4
│   ├── svc5
│   └── svc6
├── manifests
│   ├── ic-common.yaml
│   ├── ic1-mandatory.yaml
│   ├── ic1-service-nodeport.yaml
│   ├── ic2-mandatory.yaml
│   └── ic2-service-nodeport.yaml
└── src
    ├── svc1
    ├── svc2
    ├── svc3
    ├── svc4
    ├── svc5
    └── svc6

其中:

  • src下面存放着svc1~svc6的源码(包括Dockerfile);
  • manifests下面存放的是ingress controllers的yaml源文件;
  • charts下面存放的是svc1~svc6的helm chart安装包源文件。

二. 创建第一个ingress-nginx controller

ingress controller有多种实现,其中应用较广的是kubernetes官方仓库中的ingress-nginx。在bare metal上安装ingress-nginx controller十分方便,只需执行下面命令即可:

kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/mandatory.yaml

不过,考虑到我后续在环境中会安装多个ingress-nginx controller,我们需要对mandatory.yaml中的内容做些调整:

  • 首先明确多个ingress-nginx controller及其相关kubernetes object所在的namespace,默认为ingress-nginx,这里统一改为ingress-nginx-demo,yaml描述文件中所有的object的namespace也都改为ingress-nginx-demo,clusterrole、clusterrolebinding对象不归属于任何namespace,因此无需修改;

  • 接下来,将多个ingress-nginx controller能共用的kubernetes object的描述数据从mandatory.yaml中提取出来,放入ic-common.yaml中,包括:namespace: ingress-nginx-demo、deployment: default-http-backend、service: default-http-backend、serviceaccount: nginx-ingress-serviceaccount、clusterrole: nginx-ingress-demo-clusterrole、role: nginx-ingress-role、rolebinding: nginx-ingress-role-nisa-binding以及clusterrolebinding: nginx-ingress-demo-clusterrole-nisa-binding;

  • 将“缩水”后的mandatory.yaml改名为ic1-mandatory.yaml,并将其内容中的kubernetes object的name添加上“-ic1″后缀。

  • 在ic1-mandatory.yaml中nginx-ingress-controller的启动参数列表尾部添加“–ingress-class=ic1”:

// ic1-mandatory.yaml
... ...
    spec:
      serviceAccountName: nginx-ingress-serviceaccount
      containers:
        - name: nginx-ingress-controller-ic1
          image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.15.0
          args:
            - /nginx-ingress-controller
            - --default-backend-service=$(POD_NAMESPACE)/default-http-backend
            - --configmap=$(POD_NAMESPACE)/nginx-configuration-ic1
            - --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services-ic1
            - --udp-services-configmap=$(POD_NAMESPACE)/udp-services-ic1
            - --publish-service=$(POD_NAMESPACE)/ingress-nginx-ic1
            - --annotations-prefix=nginx.ingress.kubernetes.io
            - --ingress-class=ic1
... ...
  • ic-common.yaml中的nginx-ingress-role中的resourceNames列表中需添加两项:”ingress-controller-leader-ic1″和”ingress-controller-leader-ic2″:
// ic-common.yaml
... ...
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:
  name: nginx-ingress-role
  namespace: ingress-nginx-demo
rules:
  - apiGroups:
      - ""
    resources:
      - configmaps
      - pods
      - secrets
      - namespaces
    verbs:
      - get
  - apiGroups:
      - ""
    resources:
      - configmaps
    resourceNames:
      # Defaults to "<election-id>-<ingress-class>"
      # Here: "<ingress-controller-leader>-<nginx>"
      # This has to be adapted if you change either parameter
      # when launching the nginx-ingress-controller.
      - "ingress-controller-leader-ic1"
      - "ingress-controller-leader-ic2"
... ...

这两个resouceName分别给两个ingress-controller使用,当每个ingress-controller存在多副本(replicas > 1)时,多副本会通过ingress-controller-leader-icX这个configmap资源来进行leader election(选主)。以ingress-controller-ic1为例,当存在多副本时,ingress-controller-ic1的启动日志:

I0621 09:13:20.646426       7 stat_collector.go:34] changing prometheus collector from  to default
I0621 09:13:20.648198       7 status.go:196] new leader elected: nginx-ingress-controller-ic1-7c9bc49cbb-kgjvz
I0621 09:13:20.752485       7 controller.go:177] ingress backend successfully reloaded...

不过,虽然存在leader,但业务流量却是负载分担的。

  • 为ingress-nginx controller pod创建nodeport类型service

如果只是部署了ingress controller,那么外部依然无法连上ingress controller,因为ingress controller自身还没有对应的service将自己暴露到集群外部。官方文档推荐使用NodePort方式,于是我们创建了ic1-service-nodeport.yaml,让流入host:30090的流量进入ingress controller service。

总结一下ingress-controller-ic1这个ingress controller的完整创建步骤:

kubectl apply -f ic-common.yaml
kubectl apply -f ic1-service-nodeport.yaml
kubectl apply -f ic1-mandatory.yaml

三. 创建例子a

svc1是一个在容器8080端口提供http服务的服务程序。在例子a)中,我们在k8s集群中创建svc1,并创建ic1-svc1 ingress将svc1暴露在集群外面,外部请求通过svc1.tonybai.com:30090可以访问到svc1。而做到这一点,我们仅需要使用helm install一下svc1这个chart:

# helm install --name ic1-svc1 ./svc1
NAME:   ic1-svc1
LAST DEPLOYED: Thu Jun 21 20:39:25 2018
NAMESPACE: default
STATUS: DEPLOYED

RESOURCES:
==> v1/Service
NAME      TYPE       CLUSTER-IP      EXTERNAL-IP  PORT(S)  AGE
ic1-svc1  ClusterIP  10.103.210.182  <none>       80/TCP   0s

==> v1beta2/Deployment
NAME      DESIRED  CURRENT  UP-TO-DATE  AVAILABLE  AGE
ic1-svc1  1        0        0           0          0s

==> v1beta1/Ingress
NAME      HOSTS             ADDRESS  PORTS  AGE
ic1-svc1  svc1.tonybai.com  80       0s

==> v1/Pod(related)
NAME                       READY  STATUS             RESTARTS  AGE
ic1-svc1-5ff84d7bff-5j7tb  0/1    ContainerCreating  0         0s

NOTES:
1. Get the application URL by running these commands:

http://svc1.tonybai.com/

svc1服务以及对应的ic1-svc1 ingress创建后,我们来测试一下:

# curl svc1.tonybai.com:30090
Hello, I am svc1 for ingress-controller demo!

结果符合预期。而这一切实现的关键在于ingress-controller-demo/charts/svc1/values.yaml:

... ...
ingress:
  enabled: true
  annotations:
    # kubernetes.io/ingress.class: nginx
    # kubernetes.io/tls-acme: "true"
    kubernetes.io/ingress.class: ic1
  path: /
  hosts:
    - svc1.tonybai.com
... ...

ingress的enabled改为true,helm才会创建svc1对应的ingress。annotations中的kubernetes.io/ingress.class: ic1很关键,设定ingress的这个annotation,可以使得该ingress归属于我们上面创建的nginx-ingress-controller-ic1 ingress controller,而其他ingress controller会忽略这个ingress。

我们再来看看 ingress-controller-ic1的后台日志,当添加svc1时,日志输出:

I0621 12:39:25.406331       7 event.go:218] Event(v1.ObjectReference{Kind:"Ingress", Namespace:"default", Name:"ic1-svc1", UID:"2176416f-7550-11e8-a0e8-00163e0cd764", APIVersion:"extensions", ResourceVersion:"1877656", FieldPath:""}): type: 'Normal' reason: 'CREATE' Ingress default/ic1-svc1
I0621 12:39:25.517915       7 controller.go:177] ingress backend successfully reloaded...
W0621 12:39:28.739708       7 controller.go:773] service default/ic1-svc1 does not have any active endpoints
I0621 12:39:34.262824       7 controller.go:168] backend reload required
I0621 12:39:34.371479       7 controller.go:177] ingress backend successfully reloaded...
nginx-ingress-controller-ic1会监听到service变化,并reload nginx。

我们可以通过下面命令查看nginx-ingress-controller-ic1内部的nginx的配置文件内容:

# kubectl exec nginx-ingress-controller-ic1-7c9bc49cbb-kgjvz -n ingress-nginx-demo -- cat /etc/nginx/nginx.conf

我们可以看到有关svc1的相关内容如下:

        upstream default-ic1-svc1-http {
                least_conn;

                keepalive 32;

                server 192.168.31.9:8080 max_fails=0 fail_timeout=0;

        }

        ## start server svc1.tonybai.com
        server {
                server_name svc1.tonybai.com ;

                listen 80;

                listen [::]:80;

                set $proxy_upstream_name "-";

                location / {

                       ... ...

                        set $proxy_upstream_name "default-ic1-svc1-http";

                        set $namespace      "default";
                        set $ingress_name   "ic1-svc1";
                        set $service_name   "ic1-svc1";

                       ... ...

                        proxy_pass http://default-ic1-svc1-http;

                        proxy_redirect                          off;

                }

        }
        ## end server svc1.tonybai.com

可一看出外部到svc1.tonybai.com:30090的流量被转到service ingress-nginx-ic1:80上,进而到达nginx pod的targetPort(80)上。

四. 创建例子b

有了例子a)作为基础,理解接下来的例子就相对简单了。例子b)与a)最大的不同是svc2是一个https服务。外部通过http协议访问:svc2.tonybai.com:30090后,nginx-ingress-controller-ic1内部的nginx需要以https的方式去访问svc2。ingress-nginx ingress controller支持这种情况,仅需要在svcb的ingress annotations加上下面这个annotation:nginx.ingress.kubernetes.io/secure-backends: “true”:

// ingress-controller-demo/charts/svc2/values.yaml
... ...
ingress:
  enabled: true
  annotations:
    # kubernetes.io/ingress.class: nginx
    # kubernetes.io/tls-acme: "true"
    nginx.ingress.kubernetes.io/secure-backends: "true"
    kubernetes.io/ingress.class: ic1
  path: /
  hosts:
    - svc2.tonybai.com
 ... ...

和例子a)一样,使用helm安装svc2这个chart后,svc2这个服务就暴露出来了:

# helm install --name ic1-svc2 ./svc2

# curl http://svc2.tonybai.com:30090
Hello, I am svc2 for ingress-controller demo!

五. 创建例子c

svc3与前面两个服务均不同,因为它直接暴露的是四层的tcp服务。kubernetes ingress无法直接支持四层的服务端口暴露,我们需要在ingress controller上“动手脚”。

首先,四层的暴露的端口不能与之前的七层端口30090重叠(因为不是通过ingress来暴露svc3服务的),我们需要一个新端口:30070,我们需要在ic1-service-nodeport.yaml中增加一组nodeport:

//ingress-controller-demo/manifests/ic1-service-nodeport.yaml

apiVersion: v1
kind: Service
metadata:
  name: ingress-nginx-ic1
  namespace: ingress-nginx-demo
spec:
  type: NodePort
  ports:
  - name: http
    port: 80
    targetPort: 80
    nodePort: 30090
    protocol: TCP
  - name: tcp
    port: 30070
    targetPort: 30070
    nodePort: 30070
    protocol: TCP
  selector:
    app: ingress-nginx-ic1

注意这里两组nodeport中的port不能一样,否则kubernetes会用下面的一组覆盖上面的那组。这里我们暴露30070这个nodeport,service的集群内port也是30070,后面的endpoint中的容器(即nginx-ingress-controller-ic1 pod)监听的也是30070。

接下来,要让nginx-ingress-controller-ic1 pod也监听30070,我们没法用ingress实现,但是ingress-nginx ingress controller支持通过一个名为:tcp-services-ic1的configmap来配置:

//ingress-controller-demo/manifests/ic1-mandatory.yaml
.... ...
spec:
      serviceAccountName: nginx-ingress-serviceaccount
      containers:
        - name: nginx-ingress-controller-ic1
          image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.15.0
          args:
            - /nginx-ingress-controller
            - --default-backend-service=$(POD_NAMESPACE)/default-http-backend
            - --configmap=$(POD_NAMESPACE)/nginx-configuration-ic1
            - --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services-ic1
            - --udp-services-configmap=$(POD_NAMESPACE)/udp-services-ic1
            - --publish-service=$(POD_NAMESPACE)/ingress-nginx-ic1
            - --annotations-prefix=nginx.ingress.kubernetes.io
... ...

在ic1-mandatory.yaml中,我们这样更新tcp-services-ic1 configmap的配置:

kind: ConfigMap
apiVersion: v1
metadata:
  name: tcp-services-ic1
  namespace: ingress-nginx-demo
data:
  30070: "default/ic1-svc3:8080"

大家可以看到,在configmap的data中,我们用了一个key:value的格式行,其中key就是nginx要暴露的端口:30070,value则为

<namespace/service name>:<service port>

格式的值,这里我们使用default名字空间下的ic1-svc3服务,服务端口8080。

重新apply ic1-mandatory.yaml和ic1-service-nodeport.yaml后,我们测试一下svc3服务:

# telnet svc3.tonybai.com 30070
Trying 127.0.0.1...
Connected to svc3.tonybai.com.
Escape character is '^]'.
hello
hello
world
world

svc3是一个echo服务,我们看到svc3 echo了我们输入的内容。

在nginx内部,30070是这样被暴露的:

stream {
        log_format log_stream [$time_local] $protocol $status $bytes_sent $bytes_received $session_time;

        access_log /var/log/nginx/access.log log_stream;

        error_log  /var/log/nginx/error.log;

        # TCP services

        upstream tcp-30070-default-ic1-svc3-8080 {

                server                  192.168.28.13:8080;

        }
        server {

                listen                  30070;

                listen                  [::]:30070;

                proxy_timeout           600s;
                proxy_pass              tcp-30070-default-ic1-svc3-8080;

        }

        # UDP services
}

六. 创建例子d

在例子d)对应的图示中,我们建立了另外一个ingress-nginx ingress controller: nginx-ingress-controller-ic2,与nginx-ingress-controller-ic1 不同的是, nginx-ingress-controller-ic2的启动参数中含:

            - --ingress-class=ic2

用以区分ic1。ic2-mandatory.yaml和ic1-mandatory.yaml相比,就是将“rc1”字样整体替换为”ic2″即可。除此之外,有了ic1-service-nodeport.yaml的基础,ic2-service-nodeport.yaml内容也是“雷同”的。建立 nginx-ingress-controller-ic2步骤如下:

# kubectl apply -f ic2-service-nodeport.yaml
# kubectl apply -f ic2-mandatory.yaml

归属于nginx-ingress-controller-ic2的三个服务svc4、svc5和svc6等价于nginx-ingress-controller-ic1的svc1、svc2和svc3,这里就不赘述了。

# curl svc4.tonybai.com:30091
Hello, I am svc4 for ingress-controller demo!
# curl svc5.tonybai.com:30091
Hello, I am svc5 for ingress-controller demo!
# telnet  svc6.tonybai.com 30071
Trying 127.0.0.1...
Connected to svc6.tonybai.com.
Escape character is '^]'.
hello
hello
tony
tony

如果想使得ingress-nginx controller高可用,只需将其pod副本数量调大即可。

盘点Kubernetes网络问题的4种解决方案

由于在企业中部署私有云的场景会更普遍,所以在私有云中运行Kubernetes + Docker集群之前,就需要自己搭建符合Kubernetes要求的网络环境。现在的开源世界里,有很多开源组件可以帮助我们打通Docker容器和容器之间的网络,实现Kubernetes要求的网络模型。当然每种方案都有自己适合的场景,我们要根据自己的实际需要进行选择。

一、Kubernetes + Flannel

Kubernetes的网络模型假定了所有Pod都在一个可以直接连通的扁平的网络空间中,这在GCE(Google Compute Engine)里面是现成的网络模型,Kubernetes假定这个网络已经存在。而在私有云里搭建Kubernetes集群,就不能假定这个网络已经存在了。我们需要自己实现这个网络假设,将不同节点上的Docker容器之间的互相访问先打通,然后运行Kubernetes。

Flannel是CoreOS团队针对Kubernetes设计的一个网络规划服务,简单来说,它的功能是让集群中的不同节点主机创建的Docker容器都具有全集群唯一的虚拟IP地址。而且它还能在这些IP地址之间建立一个覆盖网络(Overlay Network),通过这个覆盖网络,将数据包原封不动地传递到目标容器内。

下面是一张它的网络原理图:

未分类

可以看到,Flannel首先创建了一个名为flannel0的网桥,而且这个网桥的一端连接docker0的网桥,另一端连接一个名为flanneld的服务进程。

Flanneld进程并不简单,它首先上连etcd,利用etcd来管理可分配的IP地址段资源,同时监控etcd中每个Pod的实际地址,并在内存中建立了一个Pod节点路由表;然后下连docker0和物理网络,使用内存中的Pod节点路由表,将docker0发给它的数据包包装起来,利用物理网络的连接将数据包投递到目标flanneld上,从而完成pod到pod之间的直接的地址通信。

Flannel之间的底层通信协议的可选余地有很多,比如UDP、VXlan、AWS VPC等等。只要能通到对端的Flannel就可以了。源Flannel封包,目标Flannel解包,最终docker0看到的就是原始的数据,非常透明,根本感觉不到中间Flannel的存在。

Flannel的安装配置网上讲的很多,在这里就不在赘述了。在这里注意一点,就是flannel使用etcd作为数据库,所以需要预先安装好etcd。

下面说说几个场景:

  1. 同一Pod内的网络通信。在同一个Pod内的容器共享同一个网络命名空间,共享同一个Linux协议栈。所以对于网络的各类操作,就和它们在同一台机器上一样,它们可以用localhost地址直接访问彼此的端口。其实这和传统的一组普通程序运行的环境是完全一样的,传统的程序不需要针对网络做特别的修改就可以移植了。这样做的结果是简单、安全和高效,也能减少将已经存在的程序从物理机或者虚拟机移植到容器下运行的难度。

  2. Pod1到Pod2的网络,分两种情况。Pod1与Pod2不在同一台主机与Pod1与Pod2在同一台主机。

  • 先说Pod1与Pod2不在同一台主机。Pod的地址是与docker0在同一个网段的,但docker0网段与宿主机网卡是两个完全不同的IP网段,并且不同Node之间的通信只能通过宿主机的物理网卡进行。将Pod的IP和所在Node的IP关联起来,通过这个关联让Pod可以互相访问。
  • Pod1与Pod2在同一台主机。Pod1和Pod2在同一台主机的话,由Docker0网桥直接转发请求到Pod2,不需要经过Flannel。
  1. Pod到Service的网络。创建一个Service时,相应会创建一个指向这个Service的域名,域名规则为{服务名}.{namespace}.svc.{集群名称}。之前Service IP的转发由iptables和kube-proxy负责,目前基于性能考虑,全部为iptables维护和转发。iptables则由kubelet维护。Service仅支持UDP和TCP协议,所以像ping的ICMP协议是用不了的,所以无法ping通Service IP。

  2. Pod到外网。Pod向外网发送请求,查找路由表, 转发数据包到宿主机的网卡,宿主网卡完成路由选择后,iptables执行Masquerade,把源IP更改为宿主网卡的IP,然后向外网服务器发送请求。

  3. 集群外部访问Pod或Service

由于Pod和Service是Kubernetes集群范围内的虚拟概念,所以集群外的客户端系统无法通过Pod的IP地址或者Service的虚拟IP地址和虚拟端口号访问到它们。为了让外部客户端可以访问这些服务,可以将Pod或Service的端口号映射到宿主机,以使得客户端应用能够通过物理机访问容器应用。

总结:Flannel实现了对Kubernetes网络的支持,但是它引入了多个网络组件,在网络通信时需要转到flannel0网络接口,再转到用户态的flanneld程序,到对端后还需要走这个过程的反过程,所以也会引入一些网络的时延损耗。另外Flannel默认的底层通信协议是UDP。UDP本身是非可靠协议,虽然两端的TCP实现了可靠传输,但在大流量、高并发应用场景下还需要反复调试,确保不会出现传输质量的问题。特别是对网络依赖重的应用,需要评估对业务的影响。

二、基于Docker Libnetwork的网络定制

容器跨主机的网络通信,主要实现思路有两种:二层VLAN网络和Overlay网络。

  • 二层VLAN网络的解决跨主机通信的思路是把原先的网络架构改造为互通的大二层网络,通过特定网络设备直接路由,实现容器点到点的之间通信。
  • Overlay网络是指在不改变现有网络基础设施的前提下,通过某种约定通信协议,把二层报文封装在IP报文之上的新的数据格式。

Libnetwork是Docker团队将Docker的网络功能从Docker核心代码中分离出去,形成一个单独的库。 Libnetwork通过插件的形式为Docker提供网络功能。 使得用户可以根据自己的需求实现自己的Driver来提供不同的网络功能。

Libnetwork所要实现的网络模型基本是这样的: 用户可以创建一个或多个网络(一个网络就是一个网桥或者一个VLAN ),一个容器可以加入一个或多个网络。 同一个网络中容器可以通信,不同网络中的容器隔离。这才是将网络从docker分离出去的真正含义,即在创建容器之前,我们可以先创建网络(即创建容器与创建网络是分开的),然后决定让容器加入哪个网络。

Libnetwork实现了5种网络模式:

  • bridge:Docker默认的容器网络驱动,Container通过一对veth pair链接到docker0网桥上,由Docker为容器动态分配IP及配置路由、防火墙等。
  • host:容器与主机共享同一Network Namespace。
  • null:容器内网络配置为空,需要用户手动为容器配置网络接口及路由。
  • remote:Docker网络插件的实现,Remote driver使得Libnetwork可以通过HTTP Resful API 对接第三方的网络方案,类似于SocketPlane的SDN方案只要实现了约定的HTTP URL处理函数以及底层的网络接口配置方法,就可以替代Docker原生的网络实现。
  • overlay:Docker原生的跨主机多子网网络方案。

未分类

Docker自身的网络功能比较简单,不能满足很多复杂的应用场景。因此,有很多开源项目用来改善Docker的网络功能,如Pipework、Weave、SocketPlane等。

举例:网络配置工具Pipework

Pipework是一个简单易用的Docker容器网络配置工具。由200多行shell脚本实现。通过使用IP、brctl、ovs-vsctl等命令来为Docker容器配置自定义的网桥、网卡、路由等。有如下功能:

  • 支持使用自定义的Linux Bridge、veth pair为容器提供通信。
  • 支持使用MacVLAN设备将容器连接到本地网络。
  • 支持DHCP获取容器的IP。
  • 支持Open vSwitch。
  • 支持VLAN划分。

Pipework简化了在复杂场景下对容器连接的操作命令,为我们配置复杂的网络拓扑提供了一个强有力的工具。对于一个基本应用而言,Docker的网络模型已经很不错了。然而,随着云计算和微服务的兴起,我们不能永远停留在使用基本应用的级别上,我们需要性能更好且更灵活的网络功能。Pipework是个很好的网络配置工具,但Pipework并不是一套解决方案,我们可以利用它提供的强大功能,根据自己的需求添加额外的功能,帮助我们构建自己的解决方案。

OVS跨主机多子网网络方案

OVS的优势是,作为开源虚拟交换机软件,它相对成熟和稳定,而且支持各类网络隧道协议,经过了OpenStack等项目的考验。这个网上很多,就不再赘述了。

三、Kubernetes集成Calico

Calico是一个纯3层的数据中心网络方案,而且无缝集成像OpenStack这种IaaS云架构,能够提供可控的VM、容器、裸机之间的IP通信。

通过将整个互联网的可扩展IP网络原则压缩到数据中心级别,Calico在每一个计算节点利用Linux Kernel实现了一个高效的vRouter来负责数据转发,而每个vRouter通过BGP协议负责把自己上运行的workload的路由信息像整个Calico网络内传播——小规模部署可以直接互联,大规模下可通过指定的BGP route reflector来完成。这样保证最终所有的workload之间的数据流量都是通过IP路由的方式完成互联的。

Calico节点组网可以直接利用数据中心的网络结构(无论是L2或者L3),不需要额外的NAT,隧道或者Overlay Network。

未分类

Calico基于iptables还提供了丰富而灵活的网络Policy,保证通过各个节点上的ACLs来提供Workload的多租户隔离、安全组以及其他可达性限制等功能。

Calico有两种布署方案,一般集群都配有SSL证书和非证书的情况。

  • 第一种无HTTPS连接etcd方案,HTTP模式部署即没有证书,直接连接etcd
  • 第二种HTTPS连接etcd集群方案,加载etcd https证书模式,有点麻烦

总结:目前Kubernetes网络最快的第一就是Calico,第二种稍慢Flannel,根据自己的网络环境条件来定。 Calico作为一款针对企业级数据中心的虚拟网络工具,借助BGP、路由表和iptables,实现了一个无需解包封包的三层网络,并且有调试简单的特点。虽然目前还有些小缺陷,比如stable版本还无法支持私有网络,但希望在后面的版本中会更加强大。

四、应用容器IP固定(参考网上资料)

Docker 1.9开始支持Contiv Netplugin,Contiv带来的方便是用户可以根据实例IP直接进行访问。

Docker 1.10版本支持指定IP启动容器,并且由于部分数据库应用对实例IP固定有需求,有必要研究容器IP固定方案的设计。

在默认的Kubernetes + Contiv的网络环境下,容器Pod的IP网络连接是由Contiv Network Plugin来完成的,Contiv Master只实现了简单的IP地址分配和回收,每次部署应用时,并不能保证Pod IP不变。所以可以考虑引入新的Pod层面的IPAM(IP地址管理插件),以保证同一个应用多次发生部署时,Pod IP始终是不变的。

作为Pod层面的IPAM,可以把这一功能直接集成在Kubernetes里。Pod作为Kubernetes的最小调度单元,原有的Kubernetes Pod Registry(主要负责处理所有与Pod以及Pod subresource相关的请求:Pod的增删改查,Pod的绑定及状态更新,exec/attach/log等操作)并不支持在创建Pod时为Pod分配IP,Pod IP是通过获取Pod Infra Container的IP来获取的,而Pod Infra Container的IP即为Contiv动态分配得来的。

在原有Kubernetes代码基础上,修改Pod结构(在PodSpec中加入PodIP)并重写了Pod Registry同时引入了两个新的资源对象:

  • Pod IP Allocator:Pod IP Allocator是一个基于etcd的IP地址分配器,主要实现Pod IP的分配与回收。Pod IP Allocator通过位图记录IP地址的分配情况,并且将该位图持久化到etcd;
  • Pod IP Recycler:Pod IP Recycler是一个基于etcd的IP地址回收站,也是实现PodConsistent IP的核心。Pod IP Recycler基于RC全名(namespace + RC name)记录每一个应用曾经使用过的IP地址,并且在下一次部署的时候预先使用处于回收状态的IP。Pod IP Recycler只会回收通过RC创建的Pod的IP,通过其他controller或者直接创建的Pod的IP并不会记录,所以通过这种方式创建的Pod的IP并不会保持不变;同时Pod IP Recycle检测每个已回收IP对象的TTL,目前设置的保留时间为一天。

这里对kubelet也需要进行改造,主要包括根据Pod Spec中指定IP进行相关的容器创建(docker run加入IP指定)以及Pod删除时释放IP操作。

Pod的创建在PaaS里主要有两种情形:

  • 应用的第一次部署及扩容,这种情况主要是从IP pool中随机分配;
  • 应用的重新部署:在重新部署时,已经释放的IP已根据RC全名存放于IP Recycle列表中,这里优先从回收列表中获取IP,从而达到IP固定的效果。

另外为了防止IP固定方案中可能出现的问题,在Kubernetes中加入了额外的REST API:包括对已分配IP的查询,手动分配/释放IP。

容器IP固定方案已测试评估中,运行基本上没问题,但稳定性有待提升。主要表现在有时不能在预期时间内停止旧Pod,从而无法释放IP造成无法复用(初步原因是由于Docker偶尔的卡顿造成无法在规定时间内停止容器),可以手动去修复。但从长期考虑,IP固定方案还需要加强稳定性并根据具体需求进行优化。

总结:目前已有多种支持Kubernetes的网络方案,比如Flannel、Calico、华为的Canal、Weave Net等。因为它们都实现了CNI规范,用户无论选择哪种方案,得到的网络模型都一样,即每个Pod都有独立的 IP,可以直接通信。区别在于不同方案的底层实现不同,有的采用基于VXLAN的Overlay实现,有的则是Underlay,性能上有区别,再有就是是否支持Network Policy了。

如何在Docker容器里创建Apache Web服务

作为Linux系统管理员,或多或少都有可能听说过Docker。因为这款软件可以有效降低运营成本,提升系统部署速度,让系统管理工作变得十分轻松。

但这一切也不是变魔术,Docker只是一个容器管理平台,可以跨平台运行各种应用软件及相应工具容器的平台。换句话说,容器化的软件可以在不同系统平台直接运行而不需要任何修改,而由运行在不同系统的Docker来管理容器软件的运行。另外,同虚拟机相比,创建、停止和维护这些容器都相对容易得多,如果想仔细了解Docker同虚拟机之间的差异,可以访问Docker官方网站进行了解。

本文将实例演示如何在CentOS 7系统和Ubuntu 16.04系统安装Docker,并从Docker Hub获得 Apache 2.4容器。之后,我们还会演示如何利用Aapache容器来充当一个Web服务器,显示网页内容,而我们的主机是不需要安装任何Web服务器的。

1. CentOS 和 Ubuntu安装 Docker

安装Docker比较简单,只需要使用如下命令,无论是CentOS系统还是Ubuntu系统都可以执行。该命令实际上执行一个Docker的安装脚本,该脚本会自动将Docker软件库添加到系统,然后安装相应的软件包。

# curl -fsSL https://get.docker.com | sh

如果运行这条命令系统没有反应,也可以直接访问https://get.docker.com将脚本下载下来,然后保存成”.sh”文件在系统里执行,实在不会的话,可以 点击这里 下载安装脚本。安装完成后会看到提示建议使用”docker”用户来执行程序,测试的话不太需要,实际生产环境建议使用非root用户,增加系统安全性。

安装完成后,可以使用如下命令启动docker:

# systemctl start docker
# systemctl status docker

未分类

如果看到如图所示的内容,就表示Docker已经安装成功,并且服务已经启动。使用Docker只需要运行如下命令:

# docker

想要查看特定命令的帮助信息,则使用以下命令:

# docker COMMAND --help

例如想查看Docker的版本信息,则使用以下命令:

# docker version --help
# docker version

执行结果如下图所示:

未分类

2. 创建Apache容器

Docker生态系统最吸引人的地方在于你可以任意下载并使用数以万计的已有容器。接下来,我们将创建一个名为”Rultr-web”的Apache 2.4容器,这个容器将脱离于当前终端独立运行。为实现此功能,我们需要从Docker Hub下载一个apache 2.4的镜像文件。

假设我们将VPS的8080端口重定向到容器的80端口,另外,我们不想从容器中提供web页面,而是使用”/home/user/website”作为容器中Apache的文件目录。想要实现该功能,就需要将”/home/user/website”这个VPS实际目录映射到容器中的”/usr/local/apache2/htdocs/”目录,同时注意执行以下命令时需要具有root用户权限,需要的话适当使用sudo:

# docker run -dit --name Rultr-web -p 8080:80 -v /home/user/website/:/usr/local/apache2/htdocs/ httpd:2.4

未分类

现在,可以查看一下容器的状态,理论上Apache容器已经运行起来了:

# docker ps

未分类

接着要做的就是在系统的”/home/user/website”目录里创建页面文件,以验证容器中的Apache服务器启动成功,并且目录映射正确。

# vi /home/user/website/docker.html

简单起见,”docker.html”文件只有如下内容:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Learn Docker at rultr.com</title>
</head>
<body>
<h1>Learn Docker With Us</h1> 
</body>
</html>

然后,就可以通过浏览器访问VPS的IP地址来访问”docker.html”文件了,还记得我们是将容器的80端口映射到VPS的8080端口了么,所以,访问的地址应该形如”AAA.BBB.CCC.DDD:8080/docker.html”,成功的话就可以见到如下内容:

未分类

之后就是一些操作容器的命令,可以进行容器的停止、删除以及删除容器镜像等。

# docker stop Rultr-web          //停止容器
# docker remove Rultr-web        //删除容器
# docker image remove httpd:2.4  //删除镜像

本文只是简单介绍如何安装Docker和使用容器实例,如果想深入了解Docker的使用,则请访问官方网站学习。

docker 容器搭建 lnmp 环境小记

1.阿里云容器地址

https://cr.console.aliyun.com

2.创建镜像仓库

3.虚拟机修改docker源

修改文件:/etc/docker/daemon.json

内容:

{
"registry-mirrors": ["https://78zjyej0.mirror.aliyuncs.com"]
}

4.虚拟机登录仓库账号

docker login --username= registry.cn-shenzhen.aliyuncs.com

输入账号密码,账号为阿里云账号,密码是在仓库那里另外设置的密码
有时login会出现这个问题: x509: certificate has expired or is not yet valid

这个问题是由于虚机的系统时间没有校正导致的,使用date命令查看时间是否为本地时间 ,使用命令 ntpdate cn.pool.ntp.org 校正时间,如果提示命令不存在,使用命令安装 yum instal ntp ,再使用 date 命令进行查看,确保时间为本地时间,最后重新使用 docker login 命令,输入密码即可。

5.拉取php镜像

docker pull php:7.2-fpm

尝试过很多php镜像,发现还是这个好用一点,配置文件分割清析在 /usr/local/etc 下面,php相关命令较全,在 /usr/local/bin 下可以看到很多命令,包括 phpize php-config 等等,而且改配置立即生效。

6.xdebug扩展

使用镜像开启一个容器 docker run -itd --name php php:7.2-fpm ,进入容器 docker exec -it php bash ,
使用 php -m 命令可看相关模块,笔者自己安装了 xdebug 扩展,使用命令 php -i > phpinfo.txt 获取到环境信息,复制文件里的内容到这个网址获取对应版本的 xdebug https://xdebug.org/wizard.php
,文件是下载到宿主机的,要拷贝文件到docker容器中, docker cp filename containerid:target_file_path ,到容器中解压文件并进入目录

/usr/local/bin/phpize 
./configure --with-php-config=/usr/local/bin/php-config
make && make install

安装完成,添加配置,在目录 /usr/local/etc/php/conf.d 下添加xdebug.ini文件,内容为

;zend_extension=/usr/local/lib/php/extensions/no-debug-non-zts-20170718/xdebug.so

;xdebug.remote_enable=1
;xdebug.idekey="PHPSTORM"
;xdebug.remote_connect_back=1
;xdebug.remote_port=9900
;xdebug.remote_log="/tmp/xdebug_log/"

使用的时间把”;”删除,使用xdebug生效, php -m 查看安装模块是否有xdebug。如果要安装其他模块,安装命令和配置添加类似,可以下载相应的php安装源码cp到虚拟机安装所需的扩展。(笔者曾经试过自己安装在一个centos容器中安装php环境,跑到最后达到了1.5G,最重要的是fileinfo这个扩展安装不了,提示内存不足,在网上找了很多方法都不行,最后放弃了,直接找现成的php容器)

7.提交镜像

回到宿主机,为保留刚才做的修改,所以重新创建一个镜像 git commit -m 'update php image' songzw/php-fpm:v1 ,此时使用 git images 可以看到多了一个 songzw/php-fpm:v2 镜像

8.提交镜像到阿里仓库

docker tag [ImageId] registry.cn-shenzhen.aliyuncs.com/songzw/php:[镜像版本号] 把仓库修改到阿里, docker push registry.cn-shenzhen.aliyuncs.com/songzw/php:[镜像版本号] 完成推送。

9.在php容器中需要安装composer才能安装扩展包,使用composer命令时要用到git,zip,unzip命令,所以要先 apt-get update , apt-get install git zip upzip ,重新构建镜像。

10.docker-compose构建nginx

./nginx/Dockerfile

from nginx
COPY ./*.conf /etc/nginx/conf.d/
run mkdir -p /var/nginx/logs/access /var/nginx/logs/error && chmod -R 777 /var/nginx
CMD nginx

nginx如果本地没有,则会先从远程仓库中拉取,将本地的conf配置文件拷贝到容器,在容器中创建目录并且授权,这个不是必须的,因为我本地的conf文件里配置的日志文件和错误文件是在这个目录,所有本地也要先有这个目录,如果容器起不来,使用docker logs containerid来查看日志,有可能是这个目录问题导致的,最后以nginx进程来运行容器,这个放到docker-compose里面没有生效,导致容器起不来。

11.docker-compose构建mysql

./mysql/Dockerfile

from mysql:5.6
COPY ./*.cnf /etc/mysql/mysql.conf.d/

需要在mysql目录下 touch my.cnf ,里面内容为自定义配置,可以为空,mysql:5.6镜像如果本地不存在也是从远程拉取。

12.docker-compose构建php

from songzw/php-fpm:v5
COPY *.conf /usr/local/etc/php-fpm.d/
COPY *.ini /usr/local/etc/php/conf.d/
run chown -R www-data.www-data /var/www

其中songzw/php-fpm:v5是笔者从php:7.2-fpm拉取下来进行重新构建的镜像,要创建文件 touch my.conftouch my.ini ,这样可以添加自定义配置,/var/www修改属主是因为会出现缓存文件的写权限问题

13.docker-compose.yml

最后呈现docker-compose.yml文件内容

mysql:
  build: ./mysql/
  volumes:
   - /var/mysql/data:/var/lib/mysql
   - /var/mysql/logs:/var/log/mysql
  expose:
   - "3306"
  ports:
   - 3306:3306
  env_file:
   - ./env

 php:
  build: ./php/
  volumes:
   - /var/www/html:/var/www/html
  expose:
   - "9000"
  links:
   - mysql
  command: php-fpm

 nginx:
  build: ./nginx
  volumes_from:
   - php
  volumes:
   - /var/nginx:/var/nginx
  links:
   - php
  ports:
   - "80:80"

使用 docker-compose up -ddocker-compose down 便可以搭建和销毁环境,其中nginx的配置文件在配置连接php时使用php容器ip或者 fastcgi_pass php:9000 ;,php代码连接mysql数据库时使用mysql容器ip或者别名mysql,即links对应的别名。