使用路由和iptables配置不同主机上的docker容器互相通信

docker启动时,会在宿主主机上创建一个名为docker0的虚拟网络接口,默认选择172.17.42.1/16,一个16位的子网掩码给容器提供了65534个IP地址。docker0只是一个在绑定到这上面的其他网卡间自动转发数据包的虚拟以太网桥,它可以使容器和主机相互通信,容器与容器间通信。

问题是,如何让位于不同主机上的docker容器可以通信?

最简单的思路,修改一台主机docker默认的虚拟网段,然后在各自主机上分别把对方的docker网段加入到路由表中,即可实现docker容器夸主机通信。

现有两台虚拟机

  • v1:192.168.124.51
  • v2:192.168.124.52

更改虚拟机docker0网段,修改为

  • v1:172.17.1.1/24
  • v2:172.17.2.1/24

命令如下:

#v1
sudo ifconfig docker0 172.17.1.1 netmask 255.255.255.0
sudo service docker restart

#v2
sudo ifconfig docker0 172.17.2.1 netmask 255.255.255.0
sudo service docker restart

然后在v1,v2上把对方的docker0网段加入到自己的路由表中

#v1
sudo route add -net 172.17.2.0 netmask 255.255.255.0 gw 192.168.124.52
sudo iptables -t nat -F POSTROUTING
sudo iptables -t nat -A POSTROUTING -s 172.17.1.0/24 ! -d 172.17.0.0/16 -j MASQUERADE

#v2
sudo route add -net 172.17.1.0  netmask 255.255.255.0  gw 192.168.124.51
sudo iptables -t nat -F POSTROUTING
sudo iptables -t nat -A POSTROUTING -s 172.17.2.0/24 ! -d 172.17.0.0/16 -j MASQUERADE

测试,v1,v2创建容器test1,test2

#v1
docker run --rm --name test1 -i -t base:latest bin/bash
docker inspect --format '{{.NetworkSettings.IPAddress}}' test1
#172.17.1.1

v2
docker run --rm --name test2 -i -t base:latest bin/bash
docker inspect --format '{{.NetworkSettings.IPAddress}}' test2
#172.17.2.1

主机上可以ping通对方容器ip,至此也就ok了。

Docker Ubuntu容器cron定时任务不生效解决方法

最近在Docker的Ubuntu容器中设置了一个定时备份任务,发现没有生效,安装rsyslog记录cron日志,发现cron输出了报错信息:

CRON[253]: Cannot make/remove an entry for the specified session

经过一番折腾在stackoverflow找到了解决方法:

#You can use something similar to this in your Dockerfile:
RUN sed -i '/session    required     pam_loginuid.so/c#session    required   pam_loginuid.so' /etc/pam.d/cron

意思就是注释掉/etc/pam.d/cron文件中的下面这一行:

...
session    required     pam_loginuid.so
...

变为:

...
# session    required     pam_loginuid.so
...

这样cron就可以正常工作了。

搭建带ssl认证的docker registry私有仓库

1、首先Docker pull registry默认下载最新版的镜像,我这边是2.6.2版本

未分类

2、这边考虑私有仓库部署的服务器可能没有网络,可以使用docker save -o registry.tar registry:2.6.2保存一个镜像,然后把registry.tar打包到部署包里面,下次使用docker load -i registry.tar加载到本地镜像

3、保证ssl已安装,且/ect/ssl/openssl.cnf中关于生成密码定义的字段都有(网上可以查)

docker run --entrypoint htpasswd registry:2.6.2 -Bbn ${username} ${password} > ${dir}/auth/htpasswd openssl req -x509 -days 3650 -subj "/C=CN/ST=GuangDong/L=ShenZhen/CN=Registry/O=Company/CN=test.io/" -nodes -newkey rsa:2048 -keyout ${dir}/certs/registry.key -out ${dir}/certs/registry.crt

4、这里username password是你要设置ssl认证通过的用户名和密码,这里需要你自己记住的几个目录dir下的auth,certs,data。auth是存放你生成的用户名密码文件,certs是存放生成的证书文件,data后续准备用来挂载私有仓库的镜像存放目录的

docker run -d -p 5000:5000 --privileged=true --restart=always --name ${name} 
-v ${dir}/config.yml:/etc/docker/registry/config.yml 
-v ${dir}/auth:/auth 
-e "REGISTRY_AUTH=htpasswd" 
-e "REGISTRY_AUTH_HTPASSWD_REALM=registry on test.io" 
-e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd 
-v ${dir}/certs:/certs 
-v ${dir}/data:/var/lib/registry 
-e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/registry.crt 
-e REGISTRY_HTTP_TLS_KEY=/certs/registry.key registry:2.6.2

这里的name是你要创建的私有仓库容器的名字,从挂载的三个目录我们可以看到是为了一一对应私有仓库容器内的目录结构的。这里我们可以看到私有仓库容器内存放镜像的目录为/var/lib/registry。这里说一下config.yml的映射原因:

version: 0.1
log:
  fields:
    service: registry
storage:
    cache:
        blobdescriptor: inmemory
    filesystem:
        rootdirectory: /var/lib/registry
    delete:
        enabled: true
http:
  addr: :5000
  headers:
    X-Content-Type-Options: [nosniff]
health:
  storagedriver:
    enabled: true
    interval: 10s
    threshold: 3

相比默认配置文件增加了delete:enabled:true选项,这是为了后面使用remote api支持删除镜像的,后面再说,到这里私有仓库就建好了。

5、现在要想使用私有仓库连接私有仓库并下载镜像查询删除镜像,你的主机需要的条件

/etc/hosts需要有xx.xx.xx.xx test.io的对应关系
/etc/ssl目录下有certs目录
你要在你的/etc/docker/certs.d目录下新建一个文件夹,命名为test.io:5000,然后把私有仓库${dir}/certs目录下的registry.crt证书拷贝到test.io:5000文件夹下(我这里的crt证书名字是registry.crt看上面生成的shell),最后我们使用docker login -u username -p password test.io:5000登陆。就可以开始使用私有仓库了。

6、如果你要上传镜像的话可以

docker tag test:latest test.io:5000/test:1.0
docker push test.io:5000/test:1.0

7、如果你要下载镜像的话可以

docker pull test.io:5000/test:1.0

8.现在要重点说v2版本之后的remote api改变

查询有哪些镜像:

curl -s –insecure –user username:password https://test.io:5000/v2/_catalog

这里curl使用了-s选项,因为有些情况下会出现%Total %Received %Xferd这些状态信息

未分类

查询镜像有哪些tag:curl -s –insecure –user username:password

https://test.io:5000/v2/image_name/tags/list

删除镜像:curl –header “Accept:

application/vnd.docker.distribution.manifest.v2+json” -I -X HEAD –insecure –user username:password https://test.io:5000/v2/image_name/manifests/image_tag

未分类

这里先拿到Docker-Content-Digest的值,后面从sha256都要带上(注意带上sha256)

curl –insecure –user username:password -X DELETE https://test.io:5000/v2/image_name/manifests/sha256:1583251f6052c35180381fbf28e93db0b9a26c2971f45532a5263c5dc4d18b61

这样删除就完成了。

但是,我测过有一个问题,使用curl删除之后,你再来使用curl来查询所有的镜像它还是存在的,你使用curl查询这个镜像的tag你会看到tag变为了null,然后你pull也是会失败的。也就是说你删除镜像仅仅只是阻止了pull,然后查询那里只是让tag变为了null。我自己试过把私有仓库容器的/var/lib/registry/docker/registry/v2/repositories/下对应镜像名字的文件夹删除掉,再查询就查不到了。最后删除了镜像还要回收空间,使用docker exec name bin/registry garbage-collect /etc/docker/registry/config.yml,这里name是你的私有仓库容器名字,/var/lib/registry/docker/registry/v2/blob是存放镜像的地方,使用gc垃圾回收这里的占用硬盘大小可以看到质的变化,我们可以在这里看垃圾回收的效果。

Docker指定网桥和指定网桥IP

$ docker network ls
NETWORK ID          NAME                DRIVER
7fca4eb8c647        bridge              bridge
9f904ee27bf5        none                null
cf03ee007fb4        host                host
  • Bridge
    默认bridge网络,我们可以使用docker network inspect命令查看返回的网络信息,我们使用docker run 命令是将网络自动应用到新的容器

  • Host
    如果是hosts模式,启动容器时不会获得独立的网络namespace,而是和宿主机使用同一个,容器不会有网卡和ip,但是除了网络其他方面还是独立的

  • Container
    如果是container指定的新创建的会和已经存在的容器共享一个网络namespace,不和宿主机有共享网络,也不会有自己的网卡和ip,而是和指定的容器共享,除了网络之外其他都是独立的

  • None
    docker容器有自己的网络namespace,但是和docker容器的网络配置没有关系,这个none的容器是没有网卡,ip,路由等,我们要手动指定

一、指定网桥

1、建网桥

[root@linuxea ~]# docker network create linuxea.com

af4526e387772f33b053ff2ab47e601ddf9618bc2d444770775723d76d3a1010

[root@linuxea ~]# docker network ls

NETWORK ID          NAME                DRIVER              SCOPE
3ebf99e55db8        bridge              bridge              local          
7eb855581296        host                host                local          
af4526e38777        linuxea.com         bridge              local          
58d75a1a38bc        none                null                local  

[root@linuxea ~]# 

查看linuxea.com

[root@linuxea ~]# docker network inspect linuxea.com
[


    {
        "Name": "linuxea.com",
        "Id": "af4526e387772f33b053ff2ab47e601ddf9618bc2d444770775723d76d3a1010",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "172.18.0.0/16",
                    "Gateway": "172.18.0.1/16"
                }
            ]
        },
        "Internal": false,
        "Containers": {},
        "Options": {},

下载镜像

[root@linuxea ~]# docker pull nginx

Using default tag: latest
latest: Pulling from library/nginx
6a5a5368e0c2: Pull complete 
4aceccff346f: Pull complete 
c8967f302193: Pull complete 
Digest: sha256:1ebfe348d131e9657872de9881fe736612b2e8e1630e0508c354acb0350a4566
Status: Downloaded newer image for nginx:latest

2、指定网桥

[root@linuxea ~]# docker run --network=linuxea.com -itd --name=mynginx nginx

b0ec2c7951fa5343d20218811005b16304f9ec5cb3107d06abbf60d5a94df248

[root@linuxea ~]# docker network inspect linuxea.com
[

    {
        "Name": "linuxea.com",
        "Id": "af4526e387772f33b053ff2ab47e601ddf9618bc2d444770775723d76d3a1010",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "172.18.0.0/16",
                    "Gateway": "172.18.0.1/16"
                }
            ]
        },
        "Internal": false,
        "Containers": {
            "b0ec2c7951fa5343d20218811005b16304f9ec5cb3107d06abbf60d5a94df248": {
                "Name": "mynginx",
                "EndpointID": "adaec00497b42ada6f6b251bff18a26623cfe96890a47df8b5da3c3d75582482",
                "MacAddress": "02:42:ac:12:00:02",
                "IPv4Address": "172.18.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {}
    }
]
[root@linuxea ~]# linuxea

二、指定网桥ip地址

1、指定docker0网段内的ip

我们手动指定–net=none,可以发现,容器中并没有网卡

[root@linuxea ~]# docker run --net=none --name mynginx -d -p 80:80 nginx

09b9819234338e47a8df7d3eba8daf23bf919b9fa2ea114d60742c3318dc2d69

[root@linuxea ~]# docker ps -a

CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
09b981923433        nginx               "nginx -g 'daemon off"   7 seconds ago       Up 5 seconds                            mynginx

[root@linuxea ~]# /root/in.sh mynginx

root@09b981923433:/# ip a

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default 


    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever

root@09b981923433:/# 

查看docker0地址从172.17.0.0网段

[root@LinuxEA ~]# ip addr show docker0

4: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN 


    link/ether 02:42:af:55:9a:54 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:afff:fe55:9a54/64 scope link 
       valid_lft forever preferred_lft forever

2、获取pid

创建连接文件后创建端到端网卡,将veth_db84e747c3绑定到docker0,并且启动

[root@LinuxEA ~]#  docker inspect -f '{{.State.Pid}}' mynginx
28383
[root@LinuxEA ~]# mkdir -p /var/run/netns

[root@LinuxEA ~]#  ln -s /proc/28383/ns/net /var/run/netns/28383

[root@LinuxEA ~]# ip link add veth_db84e747c3 type veth peer name x

3、安装brctl-tools

yum install bridge-utils

[root@LinuxEA ~]# brctl addif docker0 veth_db84e747c3

[root@LinuxEA ~]# ip link set veth_db84e747c3 up

[root@LinuxEA ~]# ip link set x netns 28383

此时mynginx中已经有块网卡

[root@LinuxEA mysql]# /root/in.sh mynginx

root@e224723da286:/# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default 
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
47: x@if48: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 2a:bf:7a:75:58:5f brd ff:ff:ff:ff:ff:ff

root@e224723da286:/# 

4、给新加网卡配置ip

[root@LinuxEA ~]# ip netns exec 28383 ip link set dev x name eth0

[root@LinuxEA ~]# ip netns exec 28383 ip link set eth0 up

[root@LinuxEA ~]# ip netns exec 28383 ip addr add 172.17.0.100/24 dev eth0

[root@LinuxEA ~]# ip netns exec 28383 ip route add default via 172.17.0.1

回到mynginx查看ip已经固定设置

root@e224723da286:/# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default 


    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
47: eth0@if48: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 2a:bf:7a:75:58:5f brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.100/24 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::28bf:7aff:fe75:585f/64 scope link 
       valid_lft forever preferred_lft forever

root@e224723da286:/# ping -w 3 www.baidu.com

PING www.a.shifen.com (103.235.46.39): 56 data bytes
64 bytes from 103.235.46.39: icmp_seq=0 ttl=46 time=197.858 ms
64 bytes from 103.235.46.39: icmp_seq=1 ttl=46 time=209.700 ms
64 bytes from 103.235.46.39: icmp_seq=2 ttl=46 time=196.508 ms
--- www.a.shifen.com ping statistics ---
4 packets transmitted, 3 packets received, 25% packet loss
round-trip min/avg/max/stddev = 196.508/201.355/209.700/5.926 ms

root@e224723da286:/# 

5、添加ip脚本如下

[root@linuxea ~]# cat /root/ip.sh 

#!/bin/bash
# filename: bind_addr.sh

if [ `id -u` -ne 0 ];then
    echo '必须使用root权限'
    exit
fi

if [ $# != 2 ]; then
    echo "使用方法: $0 容器名字 IP"
    exit 1
fi

container_name=$1
bind_ip=$2

container_id=`docker inspect -f '{{.Id}}' $container_name 2> /dev/null`
if [ ! $container_id ];then
    echo "容器不存在"
    exit 2
fi
bind_ip=`echo $bind_ip | egrep '^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]).){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$'`
if [ ! $bind_ip ];then
    echo "IP地址格式不正确"
    exit 3
fi

container_minid=`echo $container_id | cut -c 1-10`
container_netmask=`ip addr show docker0 | grep "inetb" | awk '{print $2}' | cut -d / -f2`
container_gw=`ip addr show docker0 | grep "inetb" | awk '{print $2}' | cut -d / -f1`

bridge_name="veth_$container_minid"
container_ip=$bind_ip/$container_netmask
pid=`docker inspect -f '{{.State.Pid}}' $container_name 2> /dev/null`
if [ ! $pid ];then
    echo "获取容器$container_name的id失败"
    exit 4
fi

if [ ! -d /var/run/netns ];then
    mkdir -p /var/run/netns
fi

ln -sf /proc/$pid/ns/net /var/run/netns/$pid

ip link add $bridge_name type veth peer name X
brctl addif docker0 $bridge_name
ip link set $bridge_name up
ip link set X netns $pid
ip netns exec $pid ip link set dev X name eth0
ip netns exec $pid ip link set eth0 up
ip netns exec $pid ip addr add $container_ip dev eth0
ip netns exec $pid ip route add default via $container_gw

三、指定网桥并且指定网桥内固定IP

1、准备工作

停掉docker,并且删除掉docker0,创建新的网桥linuxea0

[root@linuxea ~]# service docker stop
Redirecting to /bin/systemctl stop  docker.service

[root@linuxea ~]# ip link set dev docker0 down

[root@linuxea ~]# brctl delbr docker0

[root@linuxea ~]# brctl addbr linuxea0

ip段为192.168.100.0/24

[root@linuxea ~]# ip addr add 192.168.100.1/24 dev linuxea0

[root@linuxea ~]# ip link set dev linuxea0 up

[root@linuxea ~]# ip addr show linuxea0

63: linuxea0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN 

    link/ether 1e:28:a7:71:19:46 brd ff:ff:ff:ff:ff:ff
    inet 192.168.100.1/24 scope global linuxea0
       valid_lft forever preferred_lft forever

[root@linuxea ~]# 

2、下载pipwork

[root@linuxea docker]# git clone https://github.com/jpetazzo/pipework.git
Cloning into 'pipework'...
remote: Counting objects: 475, done.
remote: Total 475 (delta 0), reused 0 (delta 0), pack-reused 475
Receiving objects: 100% (475/475), 158.46 KiB | 98.00 KiB/s, done.
Resolving deltas: 100% (250/250), done.

[root@linuxea docker]# cp -rp pipework/pipework /usr/local/bin/

[root@linuxea docker]# pipework
Syntax:
pipework <hostinterface> [-i containerinterface] [-l localinterfacename] [-a addressfamily] <guest> <ipaddr>/<subnet>[@default_gateway] [macaddr][@vlan]
pipework <hostinterface> [-i containerinterface] [-l localinterfacename] <guest> dhcp [macaddr][@vlan]
pipework route <guest> <route_command>
pipework --wait [-i containerinterface]

[root@linuxea docker]# 

查看

[root@linuxea docker]# brctl show

bridge name     bridge id               STP enabled     interfaces
br-24418946eb12         8000.0242668f42e0       no       
linuxea0                8000.000000000000       no       

写入内容如下:

[root@linuxea docker]# cat /etc/sysconfig/docker | grep 'OPTIONS='
OPTIONS='
OPTIONS=--selinux-enabled -b=linuxea -H fd://

删除了docker0后将默认桥指定了linuxea0,则在创建容器时加上net=none

3、run一个服务后

[root@linuxea docker]# docker run --rm -ti --net=none nginx /bin/bash

root@b6d29d0accf0:/#

使用pipwork将linuxea0指定ip到run起的服务上

[root@linuxea ~]# docker ps -a

CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
b6d29d0accf0        nginx               "/bin/bash"         38 seconds ago      Up 35 seconds                           condescending_minsky

[root@linuxea ~]# pipework linuxea0 -i eth0 b6d29d0accf0 192.168.100.100/[email protected]

而后在查看

root@b6d29d0accf0:/# ip a

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default 

    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
65: eth0@if66: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 72:78:ef:7b:f2:9b brd ff:ff:ff:ff:ff:ff
    inet 192.168.100.100/24 brd 192.168.100.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::7078:efff:fe7b:f29b/64 scope link 
       valid_lft forever preferred_lft forever

root@b6d29d0accf0:/# 

# 默认不指定网卡设备名,则默认添加为 eth1

# 另外 pipework 不能添加静态路由,如果有需求则可以在 run 的时候加上 --privileged=true 权限在容器中手动添加,

# 但这种安全性有缺陷,可以通过 ip netns 操作

使用 ip netns 添加静态路由,避免创建容器使用 –privileged=true 选项造成一些不必要的安全问题

[root@linuxea ~]# docker inspect --format="{{ .State.Pid }}"  9f28a3f40737
15142
[root@linuxea ~]# ln -s /proc/15142/ns/net /var/run/netns/15142
[root@linuxea ~]# ip netns exec 15142 ip route add 192.168.100.0/24 dev eth0 via 192.168.100.1
[root@linuxea ~]# ip netns exec 15142 ip route
default via 192.168.100.1 dev eth0 

192.168.100.0/24 dev eth0  proto kernel  scope link  src 192.168.100.100 
[root@linuxea ~]# 

到此为止虽然,IP或者网桥指定了,事实上使用起来并不是很方便,且每次都需要指定nat,本次到此为止

  • 在容器中
route add default gw 10.0.0.1
  • 在docker宿主机上
route add -net 192.168.100.1 gw 10.0.0.1

除了默认的 docker0 网桥,用户也可以指定网桥来连接各个容器。在启动 Docker 服务的时候,使用 -b BRIDGE或–bridge=BRIDGE 来指定使用的网桥。

如果服务已经运行,那需要先停止服务,并删除旧的网桥。

$ sudo service docker stop

$ sudo ip link set dev docker0 down

$ sudo brctl delbr docker0

然后创建一个网桥 bridge0。

要使Linux可以工作在网桥模式,必须安装网桥工具bridge-utils,运行命令:

yum install bridge-utils


$ sudo brctl addbr bridge0


$ sudo ip addr add 192.168.5.1/24 dev bridge0


$ sudo ip link set dev bridge0 up

查看确认网桥创建并启动。

$ ip addr show bridge04: bridge0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state UP group default
    link/ether 66:38:d0:0d:76:18 brd ff:ff:ff:ff:ff:ff
    inet 192.168.5.1/24 scope global bridge0
       valid_lft forever preferred_lft forever

配置 Docker 服务,默认桥接到创建的网桥上。

$ echo 'DOCKER_OPTS="-b=bridge0"' >> /etc/default/docker

$ sudo service docker start

启动 Docker 服务。新建一个容器,可以看到它已经桥接到了 bridge0 上。

可以继续用 brctl show 命令查看桥接的信息。另外,在容器中可以使用 ip addr 和 ip route 命令来查看 IP 地址配置和路由信息。

brctl addbr bridge

brctl addif bridge eth0

brctl addif bridge ath0

ifconfig eth0 0.0.0.0

ifconfig bridge 10.0.0.1 netmask 255.255.255.0 up

添加iptables -t nat -A POSTROUTING -o eth1-j SNAT –to 192.168.2.173

将有线和无线都设置为10.0.0.*网段,即可通过网上邻居进行访问

当然了,要是Linux可以工作在网桥模式,必须安装网桥工具bridge-utils,运行命令:

yum install bridge-utils

或者下载bridge-utils-1.4.tar.gz进行安装,步骤如下:

编译安装bridge-utils

  • 进入到/usr/src目录下,下载bridge-utils-1.4.tar.gz :
#cd /usr/src
#wget http://launchpad.net/bridgeutils/
main/1.4/+download/bridge-utils-1.4.tar.gz
  • 解压缩:
#tar zxvf bridge-utils-1.4.tar.gz
进入bridge-utils-1.4目录:
#cd bridge-utils-1.4
  • 编译安装:
#autoconf
生成configure文件:
#./configure
#make
#make install

编译安装完成。最后将命令brctl复制到/sbin下:

# cp/usr/local/sbin/brctl    /sbin 

addbr bridge的名称 #添加bridge;

delbr bridge的名称 #删除bridge;

addif bridge的名称device的名称#添加接口到bridge;

delif bridge的名称device的名称#从bridge中删除接口

setageing bridge的名称时间 #设置老化时间,即生存周期

setbridgeprio bridge的名称 优先级#设置bridge的优先级

setfd bridge的名称时间 #设置bridge转发延迟时间

sethello bridge的名称时间 #设置hello时间

setmaxage bridge的名称时间 #设置消息的最大生命周期

setpathcost bridge的名称 端口 权重#设置路径的权值

setportprio bridge的名称 端口 优先级#设置端口的优先级

show #显示bridge列表

showmacs bridge的名称 #显示MAC地址

showstp bridge的名称 #显示bridge的stp信息

stp bridge的名称{on|off} #开/关stp

设置linux让网桥运行 配置网桥

  • 我们需要让linux知道网桥,首先告诉它,我们想要一个虚拟的以太网桥接口:(这将在主机bridge上执行,不清楚的看看测试场景)
root@bridge:~> brctl addbr br0
  • 其次,我们不需要STP(生成树协议)等。因为我们只有一个路由器,是绝对不可能形成一个环的。我们可以关闭这个功能。(这样也可以减少网络环境的数据包污染):
root@bridge:~> brctl stp br0 off
  • 经过这些准备工作后,我们终于可以做一些立竿见影的事了。我们添加两个(或更多)以太网物理接口,意思是:我们将他们附加到刚生成的逻辑(虚拟)网桥接口br0上。
root@bridge:~> brctl addif br0 eth0

root@bridge:~> brctl addif br0 eth1
  • 现在,原来我们的两个以太网物理接口变成了网桥上的两个逻辑端口。那两个物理接口过去存在,未来也不会消失。要不信的话,去看看好了。.现在他们成了逻辑网桥设备的一部分了,所以不再需要IP地址。下面我们将这些IP地址释放掉

  • 最后,启用网桥

root@bridge:~> ifconfig br0 up

可选: 我们给这个新的桥接口分配一个IP地址

root@bridge:~> ifconfig br0 10.0.3.129

或者把最后这两步合成一步:

root@bridge:~> ifconfig br0 10.0.3.129 up

就是多一个up!

这下我们做完了 。

关闭网桥命令

brctl delif ena eth1;

brctl delif ena eth0;

ifconfig ena down;

brctl delbr ena; 

Docker服务进程在启动的时候会生成一个名为docker0的网桥,容器默认都会挂载到该网桥下,但是我们可以通过添加docker启动参数-b Birdge 或更改docker配置文件来选择使用哪个网桥。

删除docker0网桥

service docker stop //关闭docker服务  


ip link set dev docker0 down //关闭docker0网桥  

ip link del dev docker0       //删除docker0网桥   

自定义网桥设置(/etc/sysconfig/network-scripts/ifcfg-br0文件)

DEVICE="br0"  
ONBOOT="yes"  
TYPE="Bridge"  
BOOTPROTO="static"  
IPADDR="10.10.10.20"  
NETMASK="255.255.255.0"  
GATEWAY="10.10.10.20"  
DEFROUTE="yes"  
NM_CONTROLLED="no"  

重启网络服务

service network restart  

查看网桥

[black@test opt]$ brctl show  
bridge name     bridge id               STP enabled     interfaces  
br0             8000.32e7297502be       no                
virbr0          8000.000000000000       yes

接下来我们需要重新启动docker,可以在启动docker服务进程时使用以下两种方式:

第一种:-b 参数指定网桥

[root@test opt]# docker -d -b br0  


INFO[0000] Listening for HTTP on unix (/var/run/docker.sock)   
INFO[0000] [graphdriver] using prior storage driver "devicemapper"   
WARN[0000] Running modprobe bridge nf_nat failed with message: , error: exit status 1   
INFO[0000] Loading containers: start.                     
......  
INFO[0000] Loading containers: done.                      
INFO[0000] Daemon has completed initialization            
INFO[0000] Docker daemon      commit=786b29d execdriver=native-0.2 graphdriver=devicemapper version=1.7.1  

不知道为什么这样启动docker 服务进程会阻塞当前终端(︶︿︶),只好重新开一个终端,然后运行一个容器

[root@test shell]# docker run -ti --rm centos:latest  
[root@3c6874559411 /]# ifconfig  
eth0      Link encap:Ethernet  HWaddr 02:42:0A:0A:0A:01    
          inet addr:10.10.10.1  Bcast:0.0.0.0  Mask:255.255.255.0  
          inet6 addr: fe80::42:aff:fe0a:a01/64 Scope:Link  
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1  
          RX packets:5 errors:0 dropped:0 overruns:0 frame:0  
          TX packets:6 errors:0 dropped:0 overruns:0 carrier:0  
          collisions:0 txqueuelen:0   
          RX bytes:418 (418.0 b)  TX bytes:508 (508.0 b)  

容器成功使用br0网桥。

第二种:修改/etc/sysconfig/docker文件

我在进行这种操作的时候遇到了一点问题,我修改了/etc/sysconfig/docker文件

[root@test opt]# vi /etc/sysconfig/docker   

# /etc/sysconfig/docker  
#  
# Other arguments to pass to the docker daemon process  
# These will be parsed by the sysv initscript and appended  
# to the arguments list passed to docker -d  

other_args="-b br0"  

接着使用service docker start启动docker服务,但是other_args并不生效,

在centos7下servicer docker start仍然会采用systemctl start docker.service命令来运行,于是我就打开/usr/lib/systemd/system/docker.service查看

[root@test opt]# vi /lib/systemd/system/docker.service   
[Unit]  
Description=Docker Application Container Engine  
Documentation=https://docs.docker.com  
After=network.target docker.socket  
Requires=docker.socket  
[Service]  
ExecStart=/usr/bin/docker -d  -H fd://  
MountFlags=slave  
LimitNOFILE=1048576  
LimitNPROC=1048576  
LimitCORE=infinity  

[Install]  
WantedBy=multi-user.target  

发现ExecStart一项并没有运行参数,于是将ExecStart改为/usr/bin/docker -d -b br0 -H fd://,运行docker服务,启动一个容器发现能够成功使用br0网桥。

在网上看到了一种更好的方法,将docker.service改为如下

[black@test ~]$ vi /usr/lib/systemd/system/docker.service   
[Unit]  
Description=Docker Application Container Engine  
Documentation=https://docs.docker.com  
After=network.target docker.socket  
Requires=docker.socket  
[Service]  
EnvironmentFile=-/etc/sysconfig/docker  
ExecStart=/usr/bin/docker -d $other_args  -H fd://  
MountFlags=slave  
LimitNOFILE=1048576  
LimitNPROC=1048576  
LimitCORE=infinity  

[Install]  
WantedBy=multi-user.target  

这个时候在other_args中添加的参数就有效了。

Docker部署基于Nodejs的Web应用

Docker

docker是一个开源的应用容器引擎,可以为我们提供安全、可移植、可重复的自动化部署的方式。docker采用虚拟化的技术来虚拟化出应用程序的运行环境。此种方式具有以下优势:

  • 每个部署的应用程序都是一个容器,彼此隔离,互不影响;

  • 服务器只需要安装docker即可运行构建好的应用程序镜像,不会涉及复杂的服务器环境配置,因为配置都在特定的应用程序所在的镜像中去配置即可;

  • 简化了自动化部署和运维的繁琐流程,只需将构建好的镜像load到服务器的docker中即可运行我们的应用程序;

  • 可以充分利用服务器的系统资源,一台服务器上可以同时运行多个容器;

未分类

docker采用的是c/s架构,Client通过接口与Server进程通信实现容器的构建,运行和发布。docker比较重要的三个核心概念如下:

  • 镜像(images):一个只读的模板,可以理解为应用程序的运行环境,包含了程序运行所依赖的环境和基本配置,镜像可以按照层级(从基础镜像开始)来构建,每一层包含特定的环境。

  • 仓库(repository):一个用于存放镜像文件的仓库,如果你对git的仓库熟悉,应该很容易理解,对,就是那个。有私有仓库和公有仓库之分。

  • 容器(container):一个运行应用程序的虚拟容器,在我们运行镜像时产生。容器包含自己的文件系统+隔离的进程空间和包含其中的进程。

前言

sharplook是一款通过大数据分析来解决客户在监控系统中存在的数据采集难、解析难、处理难的IT运维产品。在给客户部署产品的过程中涉及到比较多的环境配置和组件安装以及复杂的依赖项,这些繁琐的流程降低了安装部署的效率和产品质量。基于此,我们开发了一款可以快速便捷的安装部署套件,提供一种漂亮的安装部署流程。产品采用 Nuxt + Koa 的基础架构进行开发,其中采用nuxt来提供SSR(服务端渲染)功能,Nuxt.js是基于Vue.js的通用架构,其中集成了以下组件:

  • Vue2 (https://github.com/vuejs/vue)

  • Vue-Router (https://github.com/vuejs/vue-router)

  • Vuex (https://github.com/vuejs/vuex)

  • Vue-Meta (https://github.com/declandewet/vue-meta)

另外,Nuxt.js 使用 Webpack 和 vue-loader 、 babel-loader 来处理代码的自动化构建工作(如打包、代码分层、压缩等等)。

我们项目使用Nuxt.js作为中间件来进行UI渲染,使用Koa启动我们自己的服务器,koa2 是由 Express 原班人马打造的,致力于成为一个更小、更富有表现力、更健壮的 Web 框架。

关于如何快速搭建这样一个项目,小生之前在《vue-cli “从入门到放弃”》中介绍过vue-cli的使用,这个项目我们通过vue-cli工具,使用 nuxt-community/koa-template 模板,vue init nuxt-community/koa-template快速构建出来。具体的代码逻辑,在此不做赘述。

关于为什么选择docker来部署我们的node服务,前面已经介绍了,我们基于node的web应用涉及到的部署环境并不复杂,仅仅需要Node.js作为平台即可,由于依赖的包文件太多,而且比较大,业界还没有特别好用的开源node打包工具。做前端的同学都知道,webpack是一个功能强大的资源加载构建的打包工具,只需要将项目文件打包到一个dist文件下,打包后的文件体量小,解决了文件之间的依赖问题,提取出公共代码库,生产环境只需要部署dist文件夹即可。然而,Nodejs程序涉及到的依赖包,和资源却没法进行打包,node服务开启后仅仅是一个node进程而已。那么我们如何部署node程序呢?既然没法打包,只能将其全部文件进行部署(虽然可能存在别的问题,小生也在研究中,大神可以给点建议),于是小生就希望将其放入docker中去部署,免去生产环境node版本不一致等问题。下面小生就将如何用docker来部署Node项目的过程分享给诸位。

实战

以下将进入战备状态,请同志们准备好大脑和电脑,跟着我左手、右手一个慢动作。

环境准备

  • 安装docker,未安装的同学,请根据自己的开发环境采用不同的安装方式去安装,具体操作参考教程,不做赘述。

  • 安装成功后,可以通过docker -v查看版本号(尽量使用最新的稳定版本)。

项目准备

  • 在你的项目根目录下,添加Dockerfile文件,此文件用来配置我们自定义一个镜像所需要指定的依赖项、环境以及执行的命令等。内容格式如下:
# 指定我们的基础镜像是node,版本是v8.0.0
 FROM node:8.0.0
 # 指定制作我们的镜像的联系人信息(镜像创建者)
 MAINTAINER EOI

 # 将根目录下的文件都copy到container(运行此镜像的容器)文件系统的app文件夹下
 ADD . /app/
 # cd到app文件夹下
 WORKDIR /app

 # 安装项目依赖包
 RUN npm install
 RUN npm rebuild node-sass --force

 # 配置环境变量
 ENV HOST 0.0.0.0
 ENV PORT 8000

 # 容器对外暴露的端口号
 EXPOSE 8000

 # 容器启动时执行的命令,类似npm run start
 CMD ["npm", "start"]

关于Dockerfile文件中的关键字,解释如下:

  • FROM
    语法:FROM [:]
    解释:设置要制作的镜像基于哪个镜像,FROM指令必须是整个Dockerfile的第一个指令,如果指定的镜像不存在默认会自动从Docker Hub上下载。

  • MAINTAINER
    语法:MAINTAINER
    解释:MAINTAINER指令允许你给将要制作的镜像设置作者信息。

  • ADD
    语法:ADD
    解释:ADD指令用于从指定路径拷贝一个文件或目录到容器的指定路径中,是一个文件或目录的路径,也可以是一个url,路径是相对于该Dockerfile文件所在位置的相对路径,是目标容器的一个绝对路径。

  • WORKDIR
    语法:WORKDIR /path/to/workdir
    解释:WORKDIR指令用于设置Dockerfile中的RUN、CMD和ENTRYPOINT指令执行命令的工作目录(默认为/目录),该指令在Dockerfile文件中可以出现多次,如果使用相对路径则为相对于WORKDIR上一次的值,例如WORKDIR /data,WORKDIR logs,RUN pwd最终输出的当前目录是/data/logs。

  • RUN
    语法:① RUN #将会调用/bin/sh -c
    ② RUN [“executable”, “param1”, “param2”] #将会调用exec执行,以避免有些时候shell方式执行时的传递参数问题,而且有些基础镜像可能不包含/bin/sh
    解释:RUN指令会在一个新的容器中执行任何命令,然后把执行后的改变提交到当前镜像,提交后的镜像会被用于Dockerfile中定义的下一步操作,RUN中定义的命令会按顺序执行并提交,这正是Docker廉价的提交和可以基于镜像的任何一个历史点创建容器的好处,就像版本控制工具一样。

  • ENV
    语法:ENV
    解释:ENV指令用于设置环境变量,在Dockerfile中这些设置的环境变量也会影响到RUN指令,当运行生成的镜像时这些环境变量依然有效,如果需要在运行时更改这些环境变量可以在运行docker run时添加–env =参数来修改。
    注意:最好不要定义那些可能和系统预定义的环境变量冲突的名字,否则可能会产生意想不到的结果。

  • EXPOSE
    语法:EXPOSE [ …]
    解释:EXPOSE指令用来告诉Docker这个容器在运行时会监听哪些端口,Docker在连接不同的容器(使用–link参数)时使用这些信息。

  • CMD
    语法: ① CMD [“executable”, “param1”, “param2”] #将会调用exec执行,首选方式
    ② CMD [“param1”, “param2”] #当使用ENTRYPOINT指令时,为该指令传递默认参数
    ③ CMD [ | ] #将会调用/bin/sh -c执行
    解释:CMD指令中指定的命令会在镜像运行时执行,在Dockerfile中只能存在一个,如果使用了多个CMD指令,则只有最后一个CMD指令有效。当出现ENTRYPOINT指令时,CMD中定义的内容会作为ENTRYPOINT指令的默认参数,也就是说可以使用CMD指令给ENTRYPOINT传递参数。
    注意:RUN和CMD都是执行命令,他们的差异在于RUN中定义的命令会在执行docker build命令创建镜像时执行,而CMD中定义的命令会在执行docker run命令运行镜像时执行,另外使用第一种语法也就是调用exec执行时,命令必须为绝对路径。

其中还有其他的一些关键字:USER、ENTRYPOINT、VOLUME、ONBUILD等,如果你有兴趣可以自行研究。

在项目根目录下添加.dockerignore文件,此文件的作用类似.gitignore文件,可以忽略掉添加进镜像中的文件,写法、格式和.gitignore一样,一行代表一个忽略。本项目添加的忽略如下:

  .DS_Store
  npm-debug.log*
  selenium-debug.log
  .nuxt/
  /package-lock.json
  *.tar
  *.md

  # Editor directories and files
  .idea
  *.suo
  *.ntvs*
  *.njsproj
  *.sln

构建镜像

  • 查看目前本地docker的镜像
> docker images

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE

cd 到项目根目录下,执行以下命令

> docker build -t deploy:1.0 .

    Sending build context to Docker daemon  1.436GB
  .... 此处省略1000个字符。
  Successfully built d8f0875e967b
  Successfully tagged deploy:1.0

deploy是镜像名,1.0是镜像的版本号,到此你已经成功构建了一个新的镜像,你可以通过docker images,查看你的镜像。

> docker images
 REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
 deploy              1.0                 d8f0875e967b        3 minutes ago        2.11GB

启动镜像,测试是否成功。

> docker run -d -p 9000:8000 deploy:1.0
8aec5ee037bb253901d2c2e02c7be546580546c493576139f3789fb660f3401d

> docker ps -a
CONTAINER ID    IMAGE        COMMAND          CREATED           STATUS         PORTS                  NAMES
8aec5ee037bb    deploy:1.0   "npm start"     57 seconds ago    Up 56 seconds  0.0.0.0:9000->8000/tcp amazing_bassi

docker run -d -p 9000:8000 deploy:1.0中-d表示后台运行,-p 9000:8000表示指定本地的9000端口隐射到容器内的8000端口。 deploy:1.0为我们要运行的镜像。通过docker ps -a查看docker的进程(容器的运行本身就是一种特殊的进程)运行情况,发现我们的容器已经在运行。本地可以访问localhost:9000。

通过docker logs可以查看我们容器内应用进程的运行日志。docker logs

> docker logs 8aec5ee037bb
  npm info it worked if it ends with ok
  npm info using [email protected]
  npm info using [email protected]
  npm info lifecycle [email protected]~prestart: [email protected]
  npm info lifecycle [email protected]~start: [email protected]

  > [email protected] start /app
  > node ./server/index.js

  Server listening on 0.0.0.0:8000
   DONE  Compiled successfully in 9310ms06:55:56

  > Open http://0.0.0.0:8000
docker stop <CONTAINER ID>可以停止容器运行

 docker start <CONTAINER ID>可以启动容器运行

 docker restart <CONTAINER ID>可以重启容器

 docker rm <CONTAINER ID> -f可以强制删除在运行的容器

上传镜像(这里用上传到公共仓库来演示)

没注册DockerHub的同学,请注册DockerHub

登录docker

> docker login
Username: XXX
Password: XXX
Login Succeeded

docker tag /上传之前必须给镜像打上tag,namespace可以指定为你的docker Id

> docker tag deploy:1.0 lzqs/deploy:1.0

docker push /将镜像上传至docker的公共仓库

> docker push lzqs/deploy:1.0

上传成功后,docker logout 退出,登录 https://hub.docker.com/ 查看上传的镜像。

下载镜像

通过docker pull /下载我们的镜像。

> docker pull lzqs/deploy:1.0

生产部署

前面说了,我们可以将上传到仓库的镜像下载下来部署,但是如果镜像比较大或者部署环境压根无法联网,你是不是要跪了。所以我们采取另一种方法,将开发好的镜像直接打包保存到安装盘里面,到客户生产环境再将镜像包上传并加载到服务器的docker中即可。

在开发环境打包,docker save / .tar

> docker save lzqs/deploy:1.0 > deploy.tar

这里ls会发现目录下生成了deploy.tar的文件。部署时将此文件copy到生产环境服务器上。

确保生产服务器上已经安装了docker,若没装,请参考相关文档,若不装,对不起小生也无力了,然后在服务器上加载上传的镜像包deploy.tar。

> docker load < deploy.tar

  007ab444b234: Loading layer [==================================================>] 129.3 MB/129.3 MB
  4902b007e6a7: Loading layer [==================================================>] 45.45 MB/45.45 MB
  bb07d0c1008d: Loading layer [==================================================>] 126.8 MB/126.8 MB
  ecf5c2e2468e: Loading layer [==================================================>] 326.6 MB/326.6 MB
  7b3b4fef39c1: Loading layer [==================================================>] 352.3 kB/352.3 kB
  677f02386f07: Loading layer [==================================================>] 137.2 kB/137.2 kB
  7333bb4665b8: Loading layer [==================================================>] 55.66 MB/55.66 MB
  e292e64ffb88: Loading layer [==================================================>] 3.757 MB/3.757 MB
  ee76d0e6f6d9: Loading layer [==================================================>] 1.436 GB/1.436 GB
  33dca533c6e5: Loading layer [==================================================>] 331.8 kB/331.8 kB
  24630015679d: Loading layer [==================================================>] 35.18 MB/35.18 MB
  Loaded image: lzqs/deploy:1.0

加载成功后,docker images即可看到加载的镜像

> docker images

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
lzqs/deploy         1.0                 d8f0875e967b        About an hour ago   2.115 GB

运行lzqs/deploy镜像,成功后,在外部访问服务器的9000端口, <服务器的IP>:9000

> docker run -d -p 9000:8000 lzqs/deploy
1d0db9a5d0c8826171e501b0e86afd444fca8144b1105e63dae8d621bdda7a77

> docker ps -a
CONTAINER ID  IMAGE           COMMAND      CREATED              STATUS             PORTS                    NAMES
1d0db9a5d0c8  lzqs/deploy:1.0 "npm start"  About a minute ago   Up About a minute  0.0.0.0:9000->8000/tcp goofy_curran

docker exec -it /bin/bash 可以进入容器中执行,方便我们查看内部文件和调试

> docker exec -it 1d0db9a5d0c8 /bin/bash

root@1d0db9a5d0c8:/app#

战功,访问部署的docker应用,<服务器的IP>:9000,效果如下图:

未分类

使用bash脚本自定义创建postgres docker容器

1. 查看镜像库中postgres镜像

Docker search postgres 

2. 下载镜像

docker pull postgres

3.查看镜像

docker images

4.配置sh脚本

mkdir  postgres  (创建文件夹)

cd postgres 

mkdir data (创建数据文件夹)

touch postgres.sh (常见sh脚本文件)

vi postgres.sh(编辑脚本)

粘贴以下代码到postgres.sh文件中

#!/bin/sh  

NAME=hy-postgres  
PORT=5432  
CURDIR=`pwd`  
PASSWORD=123456  
case "$1" in  
    create)  
        port_map="-p 172.17.0.1:5432:5432"  
        volumn_map="-v $PWD/data:/data"  
        env_map="-e POSTGRES_PASSWORD=$PASSWORD"   
        docker run --name $NAME -d $env_map $port_map $volumn_map postgres:9.4.3  
        ;;  
    delete)  
        docker rm $NAME  
        ;;  
    start)  
        docker start $NAME  
        ;;        
    stop)  
        docker stop $NAME  
        ;;  
    status)  
        docker ps -a | grep $NAME  
        ;;  
    restart)  
        docker restart $NAME  
        ;;    
    bash)  
        docker exec -it $NAME bash  
        ;;  
    exec)  
        shift  
        docker exec -it $NAME $*  
        ;;  
    *)  
        echo "Usage: $0 {start|stop|status|bash|exec|restart}"  
        exit 1  
    ;;  
esac  

5. 使用脚本启动镜像

./postgres.sh create(创建镜像)

./postgres.sh start(启动镜像)

利用docker-compose安装lnmp(Nginx mariadb php7.0 )

对于Docker来说,最大的便利就是能快速的搭建起一个个的容器,容器之间可以通过网络和文件来进行通信。

之前我已经将自己的博客使用docker搭建起来了,这里简单记录一下docker-compose文件内容。

我的博客的架构为lnmp,依赖的容器有:

  • Nginx(Port:80)

  • mariadb(Port:3306)

  • wordpress+php7.0-fpm(Port:9000)

  • phpmyadmin(Port:8009)

docker-compose.yml文件内容如下

nginx:
    image: nginx:latest
    ports:
        - '80:80'
    volumes:
        - ./nginx:/etc/nginx/conf.d
        - ./logs/nginx:/var/log/nginx
        - ./jialeens:/var/www/html
    links:
        - wordpress
    restart: always

mysql:
    image: mariadb
    ports:
        - '3306:3306'
    volumes:
        - ./db-data:/var/lib/mysql
    environment:
        - MYSQL_ROOT_PASSWORD=******
    restart: always

wordpress:
    image: wordpress:4.8.0-php7.0-fpm
    ports:
        - '9000:9000'
    volumes:
        - ./jialeens:/var/www/html
    environment:
        - WORDPRESS_DB_NAME=***
        - WORDPRESS_TABLE_PREFIX=wp_
        - WORDPRESS_DB_HOST=mysql
        - WORDPRESS_DB_PASSWORD=*****
    links:
        - mysql
    restart: always
phpmyadmin:
  image: phpmyadmin/phpmyadmin
  links:
    - mysql
  environment:
    PMA_HOST: mysql
    PMA_PORT: 3306
  ports:
    - '8009:80'

Nginx配置文件:

jialeens.com.conf

server {
    listen 80;
    server_name jialeens.com www.jialeens.com;

    fastcgi_buffer_size 64k;
    fastcgi_buffers 4 64k;
    fastcgi_busy_buffers_size 128k;
    fastcgi_temp_file_write_size 128k;
    client_max_body_size 100m;
    root /var/www/html;
    index index.php;

    access_log /var/log/nginx/jialeens-access-http.log;
    error_log /var/log/nginx/jialeens-error-http.log;

    if ($host = 'jialeens.com') {
        return 301 http://www.jialeens.com$request_uri;
    }
    location ~* ^.+.(js|ico|gif|jpg|jpeg|png|html|htm)$ {
       log_not_found off;
       access_log off;
       expires 7d;
    }
    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    location ~ .php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+.php)(/.+)$;
        fastcgi_pass wordpress:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PHP_VALUE "upload_max_filesize=128M n post_max_size=128M";
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }
}

因为流量不大,所以没做fastcgi的缓存,以后有空再弄吧。

Docker OOM事件介绍

OOM(Out Of Memory)内存不足,通常是由于某些不稳定的进程占用过多的内存造成,在Docker中称为OOM事件,当容器使用的内存过多时就会发生OOM事件,这个事件是由Linux内核的内存管理机制发起,并将是使用占用内存过多的容器Kill掉,保证系统的可持续运行。Linux内核为了保证系统的稳定性而将内存划分为两大部分用户空间与内核空间

未分类

用户空间是提供给用户进程所使用的内存空间。内核空间是仅提供给内核运行的空间。用户的进程是无法访问内核空间,而内核是可以访问用户空间与内核空间。在Linux内存管理机制中还存在一个定时任务,检查计算机的内存是否足够使用,分别收集以下几个指标

  • Total page cache as page cache is easily reclaimed
  • Total free pages because they are already available
  • Total free swap pages as userspace pages may be paged out
  • Total pages managed by swapper_space although this double-counts the free swap – pages. This is balanced by the fact that slots are sometimes reserved but not used
  • Total pages used by the dentry cache as they are easily reclaimed
  • Total pages used by the inode cache as they are easily reclaimed

如果内核发现内存不足够使用时开始发起OOM的状态检查,接着调用out-of-memory函数查找使用内存最多的进程并kill掉 oom

未分类

在Docker的容器中默认是没有限制资源使用的,也就是说容器获得到CPU/内存与宿主机是一样的,为了避免OOM事件,可以给Docker的容器作一些调整

  • 通过性能测试后才放到生产环境的容器中

  • 确保主机上有足够的资源分配

  • 使用SWAP(交换空间)

  • 将容器转换到有足够内存的Docker Swarm的服务中

注意:Docker不建议手动调整–oom-score-adj与–oom-disable-kill选项来避免OOM。

升级docker的方法

1、下载docker最近稳定版

curl -sSL -O https://get.docker.com/builds/Linux/x86_64/docker-1.9.1

2、停止docker服务并备份文件

service docker stop
mv /usr/bin/docker /usr/bin/docker_bak

3、升级docker

mv docker-1.9.1 /usr/bin/docker
chmod +x /usr/bin/docker
service docker start

4、查看最新版本

docker -v

Docker默认网络、自定义网络和overlay网络介绍

前言

前面总结了Docker基础以及Docker存储相关知识,今天来总结一下Docker单主机网络的相关知识。毋庸置疑,网络绝对是任何系统的核心,他在Docker中也占有重要的作用。同样本文基于CloudMan的系列教程。感谢ColudMan无私分享。

一、Docker默认网络

在新安装docker的主机上执行

docker network ls

便能看到docker默认安装的所有网络,分别是none网络、host网络和bridge网络。

1.1 none 网络

none网络就是什么都没有的网络。挂在这个网络下的容器除了lo,没有其他任何网卡。容器run时,可以通过添加–network=none参数来指定该容器使用none网络。那么这样一个只有lo的网络有什么用呢?此处CloudMan指出:

  • none网络应用与隔离场景,一些对安全性要求高并且不需要联网的应用可以使用none网络。

  • 比如某个容器的唯一用途是生成随机密码,就可以放到none网络中避免密码被窃取。

我可以理解none网络肯定是用于隔离的,然而我好奇的是生成的随机密码如何发送到外部呢?如何被外部调用呢?这是我没有想明白的问题。有知道的希望不吝赐教!谢谢!

1.2 host 网络

连接到host网络的容器共享Docker宿主机的网络栈,即容器的网络配置与host宿主机完全一样。可以通过添加–network=host参数来指定该容器使用host网络。

在容器中可以看到host的所有网卡,并且连hostname也是host的。host网络的使用场景又是什么呢?

直接使用Docker host的网络最大的好处就是性能,如果容器对网络传输效率有较高要求,则可以选择host网络。当然不便之处就是牺牲一些灵活性,比如要考虑端口冲突问题,Docker host上已经使用的端口就不能再用了。

Docker host的另一个用途是让容器可以直接配置 host 网路。比如某些跨host的网络解决方案,其本身也是以容器方式运行的,这些方案需要对网络进行配置。

相当于该容器拥有了host主机的网络,那么其ip等配置也相同,相当于主机中套了一个与外部一模一样的容器,可以直接通过host的ip地址来访问该容器。

1.3 bridge 网络

在不指定–network参数或者–network=bridge的情况下创建的容器其网络类型都是bridge。

Docker在安装时会在宿主机上创建名为docker0的网桥,所谓网桥相当于一个虚拟交换机,如果使用上述两种方式run的容器都会挂到docker0上。

容器和docker0之间通过veth进行连接,veth相当于一根虚拟网线,连接容器和虚拟交换机,这样就使得docker0与容器连通了。

二、自定义容器网络

理论上有了上述三种网络已经足够满足普通用户的需求,但是有时候可能用户需要指定自己的网络,以此来适应某些配置,如ip地址规划等等。

2.1 创建自定义网络

Docker提供三种user-defined网络驱动:bridge,overlay和macvlan。overlay和macvlan用于创建跨主机的网络,会在下一篇文章介绍。所以本文介绍创建bridge自定义网络。命令如下:

docker network create -d bridge --subnet 172.10.0.0/24 --gateway 172.10.0.1 my_net

-d bridge表示自定义网络的驱动为bridge,–subnet 172.10.0.0/24 –gateway 172.10.0.1分别指定网段和网关。

这样就创建好了一个自动一网络,可以通过以下命令查看此网络的信息:

docker network inspect my_net

会得到此网络的配置信息,my_net是刚刚创建的网络名称,如果为bridge就是查看docker创建的默认bridge网络信息。

每创建一个自定义网络便会在宿主机中创建一个网桥(docker0是创建的默认网桥,其实原理是一致的,而且也是对等的。)。名字为br-<网络短ID>,可以通过brctl show命令查看全部网桥信息。

docker的自定义网络与OpenStack中的网络信息倒是基本一致。所以一通百通,只要docker的明白了,所有虚拟化甚至实体的网络也就基本都搞清楚了。

2.2 使用自定义网络

通过以下命令为容器指定自定义网络:

docker run -it --network my_net --ip 172.10.0.3 busybox

其实这与使用docker默认网络是一致的,都是添加–network参数参数,此处也添加了–ip参数来指定容器的ip地址。

三、不同容器之间的连通性

同一个网络(默认网络或者自定义网络)下的容器之间是能ping通的,但是不同网络之间的容器由于网络独立性的要求是无法ping通的。原因是iptables-save DROP掉了docker之间的网络,大概如下:

-A DOCKER-ISOLATION -i docker0 -o br-ac4fe2d72b18 -j DROP
-A DOCKER-ISOLATION -i br-ac4fe2d72b18 -o docker0 -j DROP
-A DOCKER-ISOLATION -i br-62f17c363f02 -o br-ac4fe2d72b18 -j DROP
-A DOCKER-ISOLATION -i br-ac4fe2d72b18 -o br-62f17c363f02 -j DROP
-A DOCKER-ISOLATION -i br-62f17c363f02 -o docker0 -j DROP
-A DOCKER-ISOLATION -i docker0 -o br-62f17c363f02 -j DROP

那么如何让不同网络之间的docker通信呢?接下来介绍容器间通信的三种方式。

3.1 IP 通信

IP通信就是直接用IP地址来进行通信,根据上面的分析需要保证两个容器处于同一个网络,那么如果不在同一个网络如何处理呢?

如果是实体机我们很容易理解,只需要为其中一台服务器添加一块网卡连接到另一个网络就可以了。容器同理,只需要为其中一个容器添加另外一个容器的网络就可以了。使用如下命令:

docker network connect my_net httpd

connect命令能够为httpd容器再添加一个my_net网络(假设httpd原来只有默认的bridge网络)。这样上面创建的busybox容器就能与此次connect的httpd容器进行通信。

3.2 Docker DNS Server

通过 IP 访问容器虽然满足了通信的需求,但还是不够灵活。因为我们在部署应用之前可能无法确定IP,部署之后再指定要访问的IP会比较麻烦。对于这个问题,可以通过docker自带的DNS服务解决。

从Docker 1.10 版本开始,docker daemon 实现了一个内嵌的DNS server,使容器可以直接通过“容器名”通信。

方法很简单,只要在启动时用–name为容器命名就可以了。

下面的命令启动两个容器bbox1和bbox2:

docker run -it --network=my_net --name=bbox1 busybox
docker run -it --network=my_net --name=bbox2 busybox

然后,bbox2就可以直接ping到bbox1了,但是使用docker DNS有个限制,只能在user-defined网络中使用。默认的bridge网络是无法使用的。

3.3 joined 容器

joined 容器是另一种实现容器间通信的方式。joined 容器非常特别,它可以使两个或多个容器共享一个网络栈,共享网卡和配置信息,joined容器之间可以通过127.0.0.1直接通信。host网络使得容器与宿主机共用同一个网络,而jointed是使得两个容器共用同一个网络。

请看下面的例子:

先创建一个httpd容器,名字为web1。

docker run -d -it --name=web1 httpd

然后创建busybox容器并通过–network=container:web1指定jointed容器为web1:

docker run -it --network=container:web1 busybox

这样busybox和web1的网卡mac地址与IP完全一样,它们共享了相同的网络栈。busybox 可以直接用127.0.0.1访问web1的http服务。

其实也很容易理解,之前的–network参数指定了默认网络或者自定义网络,而此处是指定了一个容器,那么当然意思就是使用这个容器的网络。这也有点类似上一篇文章讲到的共享存储。

joined 容器非常适合以下场景:

  • 不同容器中的程序希望通过loopback高效快速地通信,比如web server与app server。

  • 希望监控其他容器的网络流量,比如运行在独立容器中的网络监控程序。

其实就是应用于即需要独立而又需要两个容器网络高度一致的场景。

3.4 容器与外部网络的连通性

  • 容器访问外部网络

容器默认是能访问外部网络的。通过NAT,docker实现了容器对外网(此处外网不一定是互联网)的访问。

  • 外部网络访问容器

通过端口映射的方式实现外部网络访问容器,即通过-p参数实现将容器的端口映射到外部端口。

四、总结

本文简单总结了单主机情况下容器网络的相关知识和使用方案,重点是使用方案,如果需要理解原理可以直接查阅CloudMan的相关教程。下一章总结跨主机容器网络。