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对应的别名。

8 个基本的 Docker 容器管理命令

利用这 8 个命令可以学习 Docker 容器的基本管理方式。这是一个为 Docker 初学者准备的,带有示范命令输出的指南。

在这篇文章中,我们将带你学习 8 个基本的 Docker 容器命令,它们操控着 Docker 容器的基本活动,例如 运行run、 列举list、 停止stop、 查看历史纪录logs、 删除delete 等等。如果你对 Docker 的概念很陌生,推荐你看看我们的 介绍指南,来了解 Docker 的基本内容以及 如何 在 Linux 上安装 Docker。 现在让我们赶快进入要了解的命令:

如何运行 Docker 容器?

众所周知,Docker 容器只是一个运行于宿主操作系统host OS上的应用进程,所以你需要一个镜像来运行它。Docker 镜像以进程的方式运行时就叫做 Docker 容器。你可以加载本地 Docker 镜像,也可以从 Docker Hub 上下载。Docker Hub 是一个提供公有和私有镜像来进行拉取pull操作的集中仓库。官方的 Docker Hub 位于 hub.docker.com。 当你指示 Docker 引擎运行容器时,它会首先搜索本地镜像,如果没有找到,它会从 Docker Hub 上拉取相应的镜像。

让我们运行一个 Apache web 服务器的 Docker 镜像,比如 httpd 进程。你需要运行 docker container run 命令。旧的命令为 docker run, 但后来 Docker 添加了子命令部分,所以新版本支持下列命令:

root@kerneltalks # docker container run -d -p 80:80 httpd
Unable to find image 'httpd:latest' locally
latest: Pulling from library/httpd
3d77ce4481b1: Pull complete
73674f4d9403: Pull complete
d266646f40bd: Pull complete
ce7b0dda0c9f: Pull complete
01729050d692: Pull complete
014246127c67: Pull complete
7cd2e04cf570: Pull complete
Digest: sha256:f4610c3a1a7da35072870625733fd0384515f7e912c6223d4a48c6eb749a8617
Status: Downloaded newer image for httpd:latest
c46f2e9e4690f5c28ee7ad508559ceee0160ac3e2b1688a61561ce9f7d99d682

Docker 的 run 命令将镜像名作为强制参数,另外还有很多可选参数。常用的参数有:

  • -d:从当前 shell 脱离容器
  • -p X:Y:绑定容器的端口 Y 到宿主机的端口 X
  • –name:命名你的容器。如果未指定,它将被赋予随机生成的名字
  • -e:当启动容器时传递环境编辑及其值

通过以上输出你可以看到,我们将 httpd 作为镜像名来运行容器。接着,本地镜像没有找到,Docker 引擎从 Docker Hub 拉取了它。注意,它下载了镜像 httpd:latest, 其中 : 后面跟着版本号。如果你需要运行特定版本的容器,你可以在镜像名后面注明版本名。如果不提供版本名,Docker 引擎会自动拉取最新的版本。

输出的最后一行显示了你新运行的 httpd 容器的唯一 ID。

如何列出所有运行中的 Docker 容器?

现在,你的容器已经运行起来了,你可能想要确认这一点,或者你想要列出你的机器上运行的所有容器。你可以使用 docker container ls 命令。在旧的 Docker 版本中,对应的命令为 docker ps。

root@kerneltalks # docker container ls
CONTAINER ID        IMAGE               COMMAND              CREATED             STATUS              PORTS                NAMES
c46f2e9e4690        httpd               "httpd-foreground"   11 minutes ago      Up 11 minutes       0.0.0.0:80->80/tcp   cranky_cori

列出的结果是按列显示的。每一列的值分别为:

  • Container ID :一开始的几个字符对应你的容器的唯一 ID
  • Image :你运行容器的镜像名
  • Command :容器启动后运行的命令
  • Created :创建时间
  • Status :容器当前状态
  • Ports :与宿主端口相连接的端口信息
  • Names :容器名(如果你没有命名你的容器,那么会随机创建)

如何查看 Docker 容器的历史纪录?

在第一步我们使用了 -d 参数来将容器,在它一开始运行的时候,就从当前的 shell 中脱离出来。在这种情况下,我们不知道容器里面发生了什么。所以为了查看容器的历史纪录,Docker 提供了 logs 命令。它采用容器名称或 ID 作为参数。

root@kerneltalks # docker container logs cranky_cori
AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 172.17.0.2. Set the 'ServerName' directive globally to suppress this message
AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 172.17.0.2. Set the 'ServerName' directive globally to suppress this message
[Thu May 31 18:35:07.301158 2018] [mpm_event:notice] [pid 1:tid 139734285989760] AH00489: Apache/2.4.33 (Unix) configured -- resuming normal operations
[Thu May 31 18:35:07.305153 2018] [core:notice] [pid 1:tid 139734285989760] AH00094: Command line: 'httpd -D FOREGROUND'

这里我使用了容器名称作为参数。你可以看到在我们的 httpd 容器中与 Apache 相关的历史纪录。

如何确定 Docker 容器的进程?

容器是一个使用宿主资源来运行的进程。这样,你可以在宿主系统的进程表中定位容器的进程。让我们在宿主系统上确定容器进程。

Docker 使用著名的 top 命令作为子命令的名称,来查看容器产生的进程。它采用容器的名称或 ID 作为参数。在旧版本的 Docker 中,只可运行 docker top 命令。在新版本中,docker topdocker container top 命令都可以生效。

root@kerneltalks # docker container top  cranky_cori
UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD
root                15702               15690               0                   18:35               ?                   00:00:00            httpd -DFOREGROUND
bin                 15729               15702               0                   18:35               ?                   00:00:00            httpd -DFOREGROUND
bin                 15730               15702               0                   18:35               ?                   00:00:00            httpd -DFOREGROUND
bin                 15731               15702               0                   18:35               ?                   00:00:00            httpd -DFOREGROUND
root@kerneltalks # ps -ef |grep -i 15702
root     15702 15690  0 18:35 ?        00:00:00 httpd -DFOREGROUND
bin      15729 15702  0 18:35 ?        00:00:00 httpd -DFOREGROUND
bin      15730 15702  0 18:35 ?        00:00:00 httpd -DFOREGROUND
bin      15731 15702  0 18:35 ?        00:00:00 httpd -DFOREGROUND
root     15993 15957  0 18:59 pts/0    00:00:00 grep --color=auto -i 15702

在第一个输出中,列出了容器产生的进程的列表。它包含了所有细节,包括用户号uid、进程号pid,父进程号ppid、开始时间、命令,等等。这里所有的进程号你都可以在宿主的进程表里搜索到。这就是我们在第二个命令里做得。这证明了容器确实是宿主系统中的进程。

如何停止 Docker 容器?

只需要 stop 命令!同样,它采用容器名称或 ID 作为参数。

root@kerneltalks # docker container stop cranky_cori
cranky_cori

如何列出停止的或不活动的 Docker 容器?

现在我们停止了我们的容器,这时如果我们使用 ls 命令,它将不会出现在列表中。

root@kerneltalks # docker container ls
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

所以,在这种情况下,如果想要查看停止的或不活动的容器,你需要在 ls 命令里同时使用 -a 参数。

root@kerneltalks # docker container ls -a
CONTAINER ID        IMAGE               COMMAND              CREATED             STATUS                     PORTS               NAMES
c46f2e9e4690        httpd               "httpd-foreground"   33 minutes ago      Exited (0) 2 minutes ago                       cranky_cori

有了 -a 参数,现在我们可以查看已停止的容器。注意这些容器的状态被标注为 已退出exited。既然容器只是一个进程,那么用“退出”比“停止”更合适!

如何(重新)启动 Docker 容器?

现在,我们来启动这个已停止的容器。这和运行一个容器有所区别。当你运行一个容器时,你将启动一个全新的容器。当你启动一个容器时,你将开始一个已经停止并保存了当时运行状态的容器。它将以停止时的状态重新开始运行。

root@kerneltalks #  docker container start c46f2e9e4690
c46f2e9e4690
root@kerneltalks # docker container ls -a
CONTAINER ID        IMAGE               COMMAND              CREATED             STATUS              PORTS                NAMES
c46f2e9e4690        httpd               "httpd-foreground"   35 minutes ago      Up 8 seconds        0.0.0.0:80->80/tcp   cranky_cori

如何移除 Docker 容器?

我们使用 rm 命令来移除容器。你不可以移除运行中的容器。移除之前需要先停止容器。你可以使用 -f 参数搭配 rm 命令来强制移除容器,但并不推荐这么做。

root@kerneltalks # docker container rm cranky_cori
cranky_cori
root@kerneltalks # docker container ls -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

你看,一旦移除了容器,即使再使用 ls -a 命令也查看不到容器了。

Ubuntu apt 安装redis 修改redis配置

apt安装redis

sudo apt update
sudo apt install redis-server 

安装完成默认是启动了redis服务

  • 关闭
sudo service redis-server stop
  • 开启服务
sudo servcie redis-server start
  • 重启
sudo service redis-server restart

redis简单使用

redis-cli

修改redis配置 远程访问

sudo vim /etc/redis/redic.conf

注释掉本机ip

# bind 127.0.0.1 
 protected-mode no #将yes修改成no

重启一下服务: sudo service redis-server restart 就可以了。
就可以远程 RedisDesktop Manager 可视化客户端连接了。

在Ubuntu 18.04上使用apt安装Java

安装默认的JRE/JDK

安装Java的最简单方法是使用与Ubuntu一起打包的版本。默认情况下,Ubuntu 18.04包含Open JDK,它是JRE和JDK的开源版本。

该软件包将安装OpenJDK 10或11。

  • 在现在,这将安装OpenJDK 10。
  • 在2018年9月以后,这将安装OpenJDK 11。

要安装此版本,请先更新软件包索引:

sudo apt update

接下来,检查 Java 是否已经安装:

java -version

如果Java当前未安装,你将看到以下输出:

Command 'java' not found, but can be installed with:

apt install default-jre
apt install openjdk-11-jre-headless
apt install openjdk-8-jre-headless
apt install openjdk-9-jre-headless

执行以下命令来安装OpenJDK:

sudo apt install default-jre

该命令将安装Java运行时环境(JRE)。这将允许你运行几乎所有的Java软件。

验证安装:

java -version

你将看到以下输出:

openjdk version "10.0.1" 2018-04-17
OpenJDK Runtime Environment (build 10.0.1+10-Ubuntu-3ubuntu1)
OpenJDK 64-Bit Server VM (build 10.0.1+10-Ubuntu-3ubuntu1, mixed mode)

除了JRE之外,你可能还需要Java开发工具包(JDK)才能编译和运行一些特定的基于Java的软件。要安装JDK,请执行以下命令,该命令也将安装JRE:

sudo apt install default-jdk

通过检查javac Java编译器的版本来验证是否安装了JDK :

javac -version

你将看到以下输出:

javac 10.0.1

接下来,我们来看看指定我们要安装的OpenJDK版本。

安装OpenJDK的特定版本

虽然你可以安装默认的OpenJDK软件包,但你也可以安装不同版本的
OpenJDK。

OpenJDK 8

Java 8是目前的长期支持版本,虽然公共维护在2019年1月结束,但仍然得到广泛支持。要安装OpenJDK 8,请执行以下命令:

sudo apt install openjdk-8-jdk

验证安装:

java -version

你会看到这样的输出:

openjdk version "1.8.0_162"
OpenJDK Runtime Environment (build 1.8.0_162-8u162-b12-1-b12)
OpenJDK 64-Bit Server VM (build 25.162-b12, mixed mode)

也可以只安装JRE,你可以通过执行sudo apt install openjdk-8-jre来安装它。

OpenJDK 10/11

Ubuntu的存储库包含一个安装Java 10或11的软件包。在2018年9月之前,该软件包将安装OpenJDK 10.一旦Java 11发布,该软件包将安装Java 11。

要安装OpenJDK 11,请执行以下命令:

sudo apt install openjdk-11-jdk

要仅安装JRE,请使用以下命令:

sudo apt install openjdk-11-jre

安装Oracle JDK

如果你想安装由Oracle发布的正式版本Oracle JDK,则需要为要使用的版本添加新的软件包存储库。

要安装作为最新LTS版本的Java 8,请首先添加其软件包存储库:

sudo add-apt-repository ppa:webupd8team/java

当你添加存储库时,你会看到如下消息:

 Oracle Java (JDK) Installer (automatically downloads and installs Oracle JDK8). There are no actual Jav
a files in this PPA.

Important -> Why Oracle Java 7 And 6 Installers No Longer Work: http://www.webupd8.org/2017/06/why-oracl
e-java-7-and-6-installers-no.html

Update: Oracle Java 9 has reached end of life: http://www.oracle.com/technetwork/java/javase/downloads/j
dk9-downloads-3848520.html

The PPA supports Ubuntu 18.04, 17.10, 16.04, 14.04 and 12.04.

More info (and Ubuntu installation instructions):
- for Oracle Java 8: http://www.webupd8.org/2012/09/install-oracle-java-8-in-ubuntu-via-ppa.html

Debian installation instructions:
- Oracle Java 8: http://www.webupd8.org/2014/03/how-to-install-oracle-java-8-in-debian.html

For Oracle Java 10, see a different PPA: https://www.linuxuprising.com/2018/04/install-oracle-java-10-in-ubuntu-or.html

More info: https://launchpad.net/~webupd8team/+archive/ubuntu/java
Press [ENTER] to continue or Ctrl-c to cancel adding it.

按ENTER继续。然后更新你的软件包列表

sudo apt update

包列表更新后,安装Oracle Java 8:

sudo apt install oracle-java8-installer

你的系统将从Oracle下载JDK并要求你接受许可协议。接受协议并安装JDK。

管理Java版本

你可以在一台服务器上安装多个Java。你可以使用update-alternatives命令配置哪个版本是命令行上使用的默认版本。

sudo update-alternatives --config java

如果你已经在本教程中安装了所有版本的Java,则输出结果如下所示:

There are 3 choices for the alternative java (providing /usr/bin/java).

  Selection    Path                                            Priority   Status
------------------------------------------------------------
* 0            /usr/lib/jvm/java-11-openjdk-amd64/bin/java      1101      auto mode
  1            /usr/lib/jvm/java-11-openjdk-amd64/bin/java      1101      manual mode
  2            /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java   1081      manual mode
  3            /usr/lib/jvm/java-8-oracle/jre/bin/java          1081      manual mode

选择与 Java 版本关联的数字以将其用作默认值,或按下ENTER以保留当前设置。

你可以为其他Java命令执行此操作,如compiler(javac):

sudo update-alternatives --config javac

可以运行该命令的其他命令包括但不限于:keytool,javadoc和jarsigner。

设置JAVA_HOME环境变量
许多使用Java编写的程序使用JAVA_HOME环境变量来确定Java的安装位置。

要设置此环境变量,请先确定Java的安装位置。使用update-alternatives命令:

sudo update-alternatives --config java

该命令显示Java的每个安装版本及其安装路径:

There are 3 choices for the alternative java (providing /usr/bin/java).

  Selection    Path                                            Priority   Status
------------------------------------------------------------
* 0            /usr/lib/jvm/java-11-openjdk-amd64/bin/java      1101      auto mode
  1            /usr/lib/jvm/java-11-openjdk-amd64/bin/java      1101      manual mode
  2            /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java   1081      manual mode
  3            /usr/lib/jvm/java-8-oracle/jre/bin/java          1081      manual mode

Press <enter> to keep the current choice[*], or type selection number:

在这种情况下,安装路径如下所示:

OpenJDK 11位于 /usr/lib/jvm/java-11-openjdk-amd64/bin/java.
OpenJDK 8位于/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java。
Oracle Java 8位于/usr/lib/jvm/java-8-oracle/jre/bin/java。

复制首选安装的路径。然后打开/etc/environment使用nano或你最喜爱的文本编辑器:

sudo nano /etc/environment

在该文件的末尾,添加以下行,确保使用自己的复制路径替换突出显示的路径:

JAVA_HOME="/usr/lib/jvm/java-11-openjdk-amd64/bin/java"

修改此文件将为JAVA_HOME系统上的所有用户设置路径。

保存文件并退出编辑器。

现在重新加载此文件以将更改应用于当前会话:

source /etc/environment

验证是否设置了环境变量:

echo $JAVA_HOME

你会看到你刚刚设置的路径:

/usr/lib/jvm/java-11-openjdk-amd64/bin/java

其他用户需要执行该命令source /etc/environment或注销并重新登录才能应用此设置。

Nginx与Apache环境防盗链设置方法

说明:很多人的VPS流量是有限的,而一般情况下我们放在网站上的媒体文件都是可以被别人引用的,我们的文件也就成了别人的免费外链,可想而知流量会消耗的有多快,这时候设置一下防盗链还是很有必要的。

根据我们搭建的系统环境不同,我们在Nginx和Apache中设置防盗链的方法也是不同的。

Nginx防盗链方法

location ~ .*.(gif|jpg|jpeg|png|bmp|swf|mp3|wav|zip|rar)$ {
    valid_referers none blocked xirik.cn *.xirik.cn;
    if ($invalid_referer){
        return 403;
    }
    expires 30d;
}

在网站所在的配置文件*.conf中添加以上代码,添加后重启nginx就可以生效,网址记得替换成自己的。

Apache防盗链方法

首先我们需要保证开启了Apache的伪静态模块

然后把下面代码中的网址替换成自己的,复制到网站根目录下的伪静态文件.htaccess中即可生效。

RewriteEngine On
RewriteCond %{HTTP_REFERER} !^http://xirik.cn/.*$ [NC]
RewriteCond %{HTTP_REFERER} !^http://xirik.cn$ [NC]
RewriteCond %{HTTP_REFERER} !^http://www.xirik.cn/.*$ [NC]
RewriteCond %{HTTP_REFERER} !^http://www.xirik.cn$ [NC]
RewriteRule .*.(gif|jpg|jpeg|png|bmp|swf|mp3|wav|zip|rar)$ http://xirik.cn/404.html [R,NC]