Docker基础与高级

最近在学习 Docker 看到国外一个牛人和自己的实践顾写下此篇文章,以用来为故而知新!

1.  Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口(类似 iPhone 的 app)。几乎没有性能开销,可以很容易地在机器和数据中心中运行。最重要的是,他们不依赖于任何语言、框架或包括系统。


2.Docker基础与高级


1.Docker安装


2.devicemapper


3.玩转net namespace


•3.1. ENV(环境变量)


port map直接使用docker默认分配的IP对外提供服务(测试中)

•5.1. 使用参数以及将docker0的ip配为机房内网网段


Docker COMMAND

•6.1. docker参数


•6.2. run


搭建私有Registry注册中心

•7.1. 下载软件


•7.2. 启动服务


•7.3. 使用


•7.4. 套一层透明代理(不推荐,有bug)


•7.5. Web UI


docker with HTTPS

•8.2.1. 创建CA(证书颁发中心)


•8.2.2. 创建服务端公钥和私钥


•8.2.3. 创建客户端公钥和私钥


•8.2.4. 移除服务端私钥、客户端私钥密码


•8.2.5. 使用


•8.2.6. 管理


•8.1. 原理


•8.2. 使用


Docker Web-UI(shipyard)

•9.5.1. 页面上的Images(http://192.168.1.1:8000/images/)进行镜像删除要注意


•9.5.2. server端管理


•9.5.3. 不建议生产使用,可作为学习借鉴


•9.4.1. 登录页面


•9.4.2. 接受agent注册


•9.3.1. 下载镜像


•9.3.2. 启动容器(自动注册到server)


•9.2.1. 下载镜像


•9.2.2. 启动容器(自动完成部署)


•9.2.3. 验证


•9.1. 工作原理


•9.2. server配置


•9.3. agent配置


•9.4. 页面配置


•9.5. 注意


镜像制作

•10.1. 远程编译Dockerfile


11.内置bridge(nat)和自建网络桥接使用区别Docker Event事件监听

•12.1. 方法1:使用remote api


•12.2. 方法2:使用unix socket


•12.3. 方法3: 使用docker events命令


神器

•13.1. nsenter(无需sshd、无需attach也可以登录容器)


FAQ

•14.5.1. HTTP code 403 while uploading metadata: invalid character ‘<‘ looking for beginning of value


•14.5.2. dial tcp 127.0.0.1:5000: connection refused


•14.1. sshd服务起不来


•14.2. ulimit无法更改open-file、max processes


•14.3. 改变/var/lib/docker路径


•14.4. 将指定镜像标识为latest


•14.5. docker push报错


•14.6. CMD 和 ENTRYPOINT的区别


tag: cloud, virtual, docker, lxc

 

1. Docker安装

按照官方说明:红帽6、centos均通过epel源,执行yum install docker-io进行docker安装,启动服务是service docker start


经测试,红帽6、centos也可以通过在官网上下载编译好的二进制文件到本地也可以使用,但需要提前手动执行service cgconfig start来挂载cgroup,然后./docker-latest -d来启动服务。
下载地址:https://get.docker.io/builds/Linux/x86_64/docker-latest

https://get.docker.io/builds/Linux/x86_64/docker-1.0.1

 

但是官方提示需要内核大于3.8版本,否则可能会有问题。el>3.8

 

2. devicemapper

扩容存储池大小、扩容容器文件系统大小
https://www.dockboard.org/resizing-docker-containers-with-the-device-mapper-plugin/

实验成功,但是一旦容器关闭再启动,就会报错,还得根据文档再做一次dmsetup load; dmsetup resume才能成功启动容器(但是如果不先启动容器,就无法使用dmsetup命令来resume),因此能否用于生产环境有待继续探索

 


3. 玩转net namespace

首先要支持ip netns指令。而红帽6及epel均不支持,解决方法:
yum install -y http://rdo.fedorapeople.org/rdo-release.rpm
yum update -y iproute


ip netns
直接执行这个命令(或ip netns list)读取的是/var/run/netns下的文件名,因此若不存在/var/run/netns,需要mkdir -p /var/run/netns


配置像LXC一样的网络
I. 宿主配置
  1. 宿主上升级iproute包,以便支持ip netns指令:
    yum install -y http://rdo.fedorapeople.org/rdo-release.rpm
    yum update -y iproute

  2. 在宿主上配置好桥接:
    一. 方法1(不推荐): 敲命令配置桥接(很容易导致网络中断,需要ILO连上操作)
      1) 创建桥接网卡br1并激活:brctl addbr br1; ip link set br1 up
      2) 配置br1的mac地址,和宿主准备桥接的网卡mac相同,通常为内网网卡eth1:ip link set br1 address xx:xx:xx:xx:xx:xx
      3) 给br1配置一个ip地址,或者将eth1的ip地址配置在br1上,2种方法任选其一都可行:
      前者:
      ifconfig br1 192.168.2.1 netmask 255.255.255.0
      后者:
      ifconfig eth1 0.0.0.0; ifconfig br1 192.168.2.2 netmask 255.255.255.0
      4) 配置宿主网关,从br1出
      ip ro del default
      ip ro add default via 192.168.2.254 dev br1
      5) 将eth1桥接至br1:
      brctl addif br1 eth1
    二. 方法2(推荐):写网卡配置文件
      ifcfg-br1:
  DEVICE="br1"
  TYPE="Bridge"
  NM_CONTROLLED="no"
  ONBOOT="yes"
  BOOTPROTO="static"
  IPADDR=192.168.2.2
  NETMASK=255.255.255.0

      ifcfg-eth1:
  DEVICE="eth1"
  BRIDGE="br1"
  BOOTPROTO="none"
  NM_CONTROLLED="no"
  ONBOOT="yes"
  TYPE="Ethernet"

      注意:要在/etc/sysconfig/network-scripts/ifup-eth里if [ "${TYPE}" = "Bridge" ]; then -> fi段落最后(fi前)加个ip link set br1 address $(get_hwaddr eth1),防止桥接网卡mac地址随机生成导致网络暂时中断

    service network restart  # 重启网络生效

II. 容器配置:
  1. 启动docker容器:
      docker run -t -i -d --name="net_test" --net=none centos:latest /bin/bash
      记录下输出(即CONTAINER ID),然后通过docker inspect -f '{{.State.Pid}}' CONTAINER ID获得该容器的pid(也即容器首进程在宿主上的pid),假设为1000
  2. 为容器创建网卡命名空间,建立点对点连接(容器命名空间网卡和宿主上生成的网卡点对点)
      mkdir -p /var/run/netns  #创建网络命名空间目录,ip netns会读取该目录下的文件名
      ln -s /proc/1000/ns/net /var/run/netns/1000  #将网络命名空间文件软链接到/var/run/netns,以便ip netns能够读取
      ip link add vethA type veth peer name vethB  #在宿主上创建2张直连网卡(vethA与vethB直连),将vethA作为容器里的网卡,vethB作为宿主上能看到的网卡
      ip link set vethB up   # 激活网卡vethB
      ip link set vethA netns 1000  # 将刚才创建的网卡归位网络命名空间
      配置vethA网卡参数:
        ip netns exec 1000 ip link set vethA name eth1
        ip netns exec 1000 ip addr add 192.168.2.3/24 dev eth1
        ip netns exec 1000 ip link set eth1 up
        ip netns exec 1000 ip route add default via 192.168.2.254 dev eth1
      brctl addif br1 vethB   # 将eth1桥接至br1
  3. 测试:
      docker attach登录容器,查看是否能ping通网关及其他子网或公网

 


3.1. ENV(环境变量)

Dockerfile支持ENV参数,表示启动容器时候设置变量。

 

只在CMD启动的进程export设置变量,而不是将变量赋值命令写入/etc/profile等脚本里,因此通过ssh方式登录容器获得的shell是没有这个变量的

 

4. port map

docker支持端口映射,通过iptables DNAT将宿主上的端口转发至容器ip对应端口。

 

虽然配置了端口映射后,在宿主上通过netstat -lntpu可以看到docker进程会监听这个端口,但是还没发现其作用,因为流量直接从iptables就进入容器里。

docker run -p、docker run -P、docker port作用:
docker run -P 就是将image定好的port给做个端口映射(若没指定-p,则外部端口随机)
docker run -p "8080:80" 启动容器时候做端口映射:宿���的0.0.0.0:8080 -> 容器80
docker run -P -p "8080:80" 假如image已经有一个port 22的配置,那么就会做2个端口映射:宿主0.0.0.0:xxxxx -> 容器22、宿主0.0.0.0:8080 -> 容器80
docker port 是查看容器已经做了端口映射的端口被映射到了哪个端口上,其实直接用docker ps就能看到,使用docker port可能是为了方便二次开发

 


5. 直接使用docker默认分配的IP对外提供服务(测试中)

 

5.1. 使用参数以及将docker0的ip配为机房内网网段

将宿主eth1桥接到docker0上,将docker0的ip更改为原来eth1的ip(机房内网网段)

 

存在一个问题:docker run时候分配的ip是从1开始,到254。因此存在和网关或者其他机器ip冲突的可能,无法避免。因此docker分配ip不会做ping检查是否存活


docker run分配出去的ip,docker kill并且docker rm都不会收回并重新使用,而是重启docker daemon才会将ip收回

–iptables=false
使用这个参数后,就不会再往iptables里生成nat、forward等信息了。

这样启动的容器,登录容器能看到网关是宿主docker0的ip,这样网络是通的,是可以访问外网,但路是这么走的:
1. 容器里的数据包将数据经过point-to-point网卡传送到宿主的对应veth网卡上
2. 宿主veth网卡接收到数据包后发现网关是254,于是通过icmp数据包告知网关是254,然后容器发送数据包时自动将网关更改为254,可以从ping的输出看到:
[ 17:37:23-root@21e77bf38fc0:~ ]#ping www.baidu.com
PING www.a.shifen.com (115.239.210.27) 56(84) bytes of data.
64 bytes from 115.239.210.27: icmp_seq=1 ttl=55 time=13.9 ms
From 192.168.3.1: icmp_seq=2 Redirect Host(New nexthop: 192.168.3.254)
64 bytes from 115.239.210.27: icmp_seq=2 ttl=55 time=13.6 ms
From 192.168.3.1: icmp_seq=3 Redirect Host(New nexthop: 192.168.3.254)
64 bytes from 115.239.210.27: icmp_seq=3 ttl=55 time=13.6 ms
From 192.168.3.1: icmp_seq=4 Redirect Host(New nexthop: 192.168.3.254)
64 bytes from 115.239.210.27: icmp_seq=4 ttl=55 time=14.0 ms
From 192.168.3.1: icmp_seq=5 Redirect Host(New nexthop: 192.168.3.254)
64 bytes from 115.239.210.27: icmp_seq=5 ttl=55 time=14.7 ms
From 192.168.3.1: icmp_seq=6 Redirect Host(New nexthop: 192.168.3.254)
64 bytes from 115.239.210.27: icmp_seq=6 ttl=55 time=13.8 ms
64 bytes from 115.239.210.27: icmp_seq=7 ttl=55 time=13.7 ms
From192.168.3.1: icmp_seq=8 Redirect Host(New nexthop: 192.168.3.254)
64 bytes from 115.239.210.27: icmp_seq=8 ttl=55 time=13.8 ms
64 bytes from 115.239.210.27: icmp_seq=9 ttl=55 time=13.6 ms
64 bytes from 115.239.210.27: icmp_seq=10 ttl=55 time=13.5 ms
From 192.168.3.1: icmp_seq=11 Redirect Host(New nexthop: 192.168.3.254)
64 bytes from 115.239.210.27: icmp_seq=11 ttl=55 time=13.8 ms
64 bytes from 115.239.210.27: icmp_seq=12 ttl=55 time=14.1 ms
64 bytes from 115.239.210.27: icmp_seq=13 ttl=55 time=13.8 ms
64 bytes from 115.239.210.27: icmp_seq=14 ttl=55 time=13.6 ms
64 bytes from 115.239.210.27: icmp_seq=15 ttl=55 time=13.7 ms
64 bytes from 115.239.210.27: icmp_seq=16 ttl=55 time=13.8 ms
From 192.168.3.1: icmp_seq=17 Redirect Host(New nexthop: 192.168.3.254)
64 bytes from 115.239.210.27: icmp_seq=17 ttl=55 time=13.6 ms
64 bytes from 115.239.210.27: icmp_seq=18 ttl=55 time=13.8 ms
64 bytes from 115.239.210.27: icmp_seq=19 ttl=55 time=13.7 ms
64 bytes from 115.239.210.27: icmp_seq=20 ttl=55 time=14.1 ms
64 bytes from 115.239.210.27: icmp_seq=21 ttl=55 time=13.7 ms
64 bytes from 115.239.210.27: icmp_seq=22 ttl=55 time=13.8 ms
64 bytes from 115.239.210.27: icmp_seq=23 ttl=55 time=13.7 ms
64 bytes from 115.239.210.27: icmp_seq=24 ttl=55 time=13.9 ms
64 bytes from 115.239.210.27: icmp_seq=25 ttl=55 time=14.2 ms
64 bytes from 115.239.210.27: icmp_seq=26 ttl=55 time=13.7 ms
64 bytes from 115.239.210.27: icmp_seq=27 ttl=55 time=13.7 ms
64 bytes from 115.239.210.27: icmp_seq=28 ttl=55 time=13.8 ms
64 bytes from 115.239.210.27: icmp_seq=29 ttl=55 time=13.9 ms
64 bytes from 115.239.210.27: icmp_seq=30 ttl=55 time=14.1 ms
64 bytes from 115.239.210.27: icmp_seq=31 ttl=55 time=13.9 ms
64 bytes from 115.239.210.27: icmp_seq=32 ttl=55 time=13.9 ms
64 bytes from 115.239.210.27: icmp_seq=33 ttl=55 time=14.0 ms
64 bytes from 115.239.210.27: icmp_seq=34 ttl=55 time=13.7 ms
64 bytes from 115.239.210.27: icmp_seq=35 ttl=55 time=14.0 ms
64 bytes from 115.239.210.27: icmp_seq=36 ttl=55 time=14.4 ms
64 bytes from 115.239.210.27: icmp_seq=37 ttl=55 time=13.9 ms

 

docker服务启动时候会把内核参数ip.forward给打开(数据包转发)

 

6. Docker COMMAND

 

6.1. docker参数

–api-enable-cors
开启cors,以便浏览器能够通过ajax调用。但是若开启了tls,使用cors就变得困难了,目前网络上还未找到解决方案

 


6.2. run

–link:2个容器互通
其实就做3件事:
1. 若有端口映射,则在iptables的FORWARD链里将端口ACCEPT
2. /etc/hosts:做link的容器的/etc/hosts能看到被link的容器的hosts条目
3. 环境变量:做link的容器可以看到被link的容器的环境变量(仅为--env变量),如:ALIAS_ENV_变量名、ALIAS_NAME=xxx


–volume: 目录共享
支持2种模式:
1. 从宿主挂载:-v /tmp:/tmp/foo 表示将宿主的/tmp目录挂载至容器的/tmp/foo目录,可读可写,和mount --bind的效果类似
2. 容器之间共享:
  启动第一个容器时带参数-v /tmp/foo表示在宿主上创建/var/lib/docker/vfs/dir/xxxxx(id,但不是容器id),然后挂进容器的/tmp/foo目录;
  启动第二个容器时带参数--volumes-from=b5f8320cf019(*第一个容器id)表示和第一个容器共享挂载,因此第二个容器启动后也能从df -h看到/tmp/foo目录被挂载。从inspect也可以很容易看出来(2个容器的inspect以下内容相同):
    "Volumes": {
        "/opt": "/var/lib/docker/vfs/dir/a7b1b03773d9391718b8524e7ac001bb877eb6d0596fa2a4328435d8c49f2415"
    },
    "VolumesRW": {
        "/opt": true
    }

相关推荐