20190222

在容器中设置静态ip,这似乎又是一个过时的老话题,在讨论群中仍然有朋友为此感到困惑。我致力于解决这些小问题和在使用中容器落地的问题。为此,我又写了这篇文章来描述容器中使用静态ip,和不使用静态ip了解的技巧。

在正式配置docker-compose之前,我们需要先了解link,因为在我看来在容器中使用固定ip是件没有必要的事情,使用ip只是我们脑中长久的一个使用习惯。而在docker中link已经帮我们解决了这个麻烦事,并提供了更简单的方式。

那么,通常来讲,在这个问题上产生疑问的,必然是在使用两个以上的容器。那就有必要了解depends_on。在使用link的同时,我当然也会叙述另外一个常用的选项depends_on,它非常有用。并且我会做简单的比较。

阅读本篇文章,你将了解,docker-compose中3版本的使用,以及link使用方式和depends_on的技巧。

容器间互联#

尽管,我们要解决的是单机网络,也不妨先简单介绍下跨主机和不跨主机容器间互联的区别

不跨主机互联可以采用默认的bridge网络,docker0桥在物理机上,而后创建的容器,容器内有eth0,另外一侧在物理机的docker0,而docker0可以理解成一个虚拟交换机。这样同一个交换机内的容器就可以直接进行互联。

除此之外,还可以使用host网络模式,共用宿主机网络,这样一来容器和宿主机使用同一个网络,不隔离网络名称空间,网卡信息,就不存在网络上的问题。

最后还可以采用联盟式容器解决网络问题

容器跨主机访问实际上是做了DNAT,将容器发布出去,这些规则在iptables中可以看到

Chain DOCKER (3 references)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 ACCEPT     tcp  --  !br-77c0aabda308 br-77c0aabda308  0.0.0.0/0            172.18.0.2           tcp dpt:26379
    0     0 ACCEPT     tcp  --  !br-77c0aabda308 br-77c0aabda308  0.0.0.0/0            172.18.0.2           tcp dpt:6379
    0     0 ACCEPT     tcp  --  !br-77c0aabda308 br-77c0aabda308  0.0.0.0/0            172.18.0.3           tcp dpt:1194
    0     0 ACCEPT     tcp  --  !br-77c0aabda308 br-77c0aabda308  0.0.0.0/0            172.18.0.3           tcp dpt:443

而对于互相双方来讲,是看不到后面的容器的,访问的是通过端口转发到真正的容器端口上。如果你要跨主机访问,就不能使用容器的ip,只能使用宿主机的ip和容器映射的端口通过iptables转发访问。

[marksugar@www.linuxea.com ~]# telnet 172.25.50.250 6379
Trying 172.25.50.250...
Connected to 172.25.50.250.
Escape character is '^]'.

当然,也有例外,如果是叠加的方式就不需要在物理机做端口映射,直接通过隧道访问对端ip和端口

我们了解到在容器网络中的分配ip是不固定的,倘若我在第一次使用的ip地址在后面使用中发生了改变,那可能不无法正常使用了!这显然并不是我们想要的。link就解决了这个问题。links似乎将会被弃用,因为它并不重要(不使用link仍然可以通过容器名称访问),我们主要来看这里的别名操作

docker-compose如下:

version: '3'
services:
  redis:
    image: marksugar/redis:5.0.0
    container_name: redis
    restart: always
    privileged: true
    environment:
      - REDIS_CONF=on
      - REQUIRE_PASS=OTdmOWI4ZTM4NTY1M2M4OTZh
      - MASTER_AUTH=OTdmOWI4ZTM4NTY1M2M4OTZh
      - MAXCLIENTS_NUM=600
      - MAXMEMORY_SIZE=4096M
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /data/redis-data:/data/redis:Z
      - /data/logs:/data/logs
    ports:
      - '6379:6379'
      - '26379:26379'

  softether:
    image: marksugar/softether:4.27
    links:
      - "redis:redisdb"
    container_name: softether4.27
    restart: always
    ports:
      - '443:443'
      - '1194:1194'

请注意,其中

    links:
      - "redis:redisdb"

softether和redis的容器ip地址分别是172.18.0.2和172.25.8.0.3,假设此时我们并不知道ip

这里在softether中链接了redis,并设置了别名,redisdb。那也就是说我们可以使用redisdb来访问redis本身。

[marksugar@www.linuxea.com /opt/2019/net]# docker exec -it softether4.27 sh
/ # apk update
fetch http://dl-cdn.alpinelinux.org/alpine/v3.8/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.8/community/x86_64/APKINDEX.tar.gz
v3.8.2-56-g4d33ed061d [http://dl-cdn.alpinelinux.org/alpine/v3.8/main]
v3.8.2-53-g53558ad6fc [http://dl-cdn.alpinelinux.org/alpine/v3.8/community]
OK: 9559 distinct packages available
/ # apk add redis
(1/1) Installing redis (4.0.11-r0)
Executing redis-4.0.11-r0.pre-install
Executing redis-4.0.11-r0.post-install
Executing busybox-1.28.4-r0.trigger
OK: 68 MiB in 35 packages
/ # redis-cli -a OTdmOWI4ZTM4NTY1M2M4OTZh -h redisdb info
Warning: Using a password with '-a' option on the command line interface may not be safe.
# Server
redis_version:5.0.0
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:a7a8d032c5a69a3f
redis_mode:standalone
os:Linux 4.18.12-1.el7.elrepo.x86_64 x86_64
arch_bits:64
multiplexing_api:epoll
atomicvar_api:atomic-builtin
gcc_version:6.4.0
process_id:10
run_id:c6162ba2b02d70c1defda6073f863af1ccb207a6
tcp_port:6379
uptime_in_seconds:263
uptime_in_days:0
hz:10
configured_hz:10
lru_clock:7302247

这说明了什么?#

我们完全可以使用容器的名称来进行访问,并不需要使用ip地址来指定。因为ip会变,名称却不会变,这是因为容器的hosts

/ # cat /etc/hosts
127.0.0.1   localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.18.0.3  d374cbffc3b0

在hosts中,已经写了ip和容器id的对应关系。你完全可以使用容器名称来进行访问。

tips

你可以不使用别名,直接使用容器名称进行访问,你会看到redis容器的ip地址

/ # ping redis
PING redis (172.18.0.2): 56 data bytes
64 bytes from 172.18.0.2: seq=0 ttl=64 time=0.093 ms
64 bytes from 172.18.0.2: seq=1 ttl=64 time=0.143 ms
64 bytes from 172.18.0.2: seq=2 ttl=64 time=0.106 ms
64 bytes from 172.18.0.2: seq=3 ttl=64 time=0.152 ms

因为在同一个网络内。

depends_on#

我们在看另外一个场景,假如此刻softether依赖于redis,在启动的时候就需要读取redis或者写入,通常情况下,redis必然要先启动,redis启动,softether在启动。这才是正确的方式,如果softether先启动,而redis还没有就绪,程序必然会报错,甚至崩溃。此时depends_on就有了用武之地

如上场景中那般,配置如下:

redis:
    image: marksugar/redis:5.0.0
    ...

softether:
    image: marksugar/softether:4.27
    ...
    depends_on:
      - redis

这样,softether会在redis启动之后启动。

如果你有多个依赖,就可以按照顺序往后写,比如mysql

redis:
    image: marksugar/redis:5.0.0
    ...
mysql:
    image:
    ...
softether:
    image: marksugar/softether:4.27
    ...
    depends_on:
      - redis
      - mysql

这样的顺序就是,softether会等待,先启动redis,在启动mysql,依次启动才到softether。由此可见,depends_on和links完全是两个不同的东西。

我非常有必要提醒,启动和准备就绪是两个概念 ,启动并不意味着一定就启动完成,就像点击开机并不意味着马上就进入桌面。其中的就绪状态则是另外的问题。请参阅启动顺序策略

docker-compose 静态ip#

默认情况下,docker会为容器分配随机(某种......)IP地址。通过使用链接,您可以将条目添加到容器的hosts文件中,并使用其IP地址映射另一个容器的名称。这样您就不需要知道其IP地址,只需使用其名称即可通过网络访问它。这种通过容器的hosts文件继而使用容器名称进行访问,这似乎已经解决了一大半人的问题。

但是,我们仍然可以使用静态ip。上面我提到,网络是会发生改变的,为了彻底解决这一点,我们将网关,子网都设置好。

在上面的默认网络中使用的ip是172.18.0.0网段。现在,我们修改它

version: '3.7'
services:
  redis:
    networks:
      linuxea:
        ipv4_address: 172.2.0.10
...        
  softether:
    networks:
      linuxea:
        ipv4_address: 172.2.0.11
...
networks:
  linuxea:
    ipam:
     driver: default
     config:
       - subnet: 172.2.0.0/24

docker-compose如下

[marksugar@www.linuxea.com /opt/2019/net]# cat docker-compose.yaml 
version: '3.7'
services:
  redis:
    image: marksugar/redis:5.0.0
    container_name: redis
    restart: always
    privileged: true
    environment:
      - REDIS_CONF=on
      - REQUIRE_PASS=OTdmOWI4ZTM4NTY1M2M4OTZh
      - MASTER_AUTH=OTdmOWI4ZTM4NTY1M2M4OTZh
      - MAXCLIENTS_NUM=600
      - MAXMEMORY_SIZE=4096M
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /data/redis-data:/data/redis:Z
      - /data/logs:/data/logs
    ports:
      - '6379:6379'
      - '26379:26379'
    networks:
      linuxea:
        ipv4_address: 172.2.0.10
  softether:
    image: marksugar/softether:4.27
    links:
      - "redis:redisdb"
    container_name: softether4.27
    restart: always
    ports:
      - '443:443'
      - '1194:1194'
    networks:
      linuxea:
        ipv4_address: 172.2.0.11
networks:
  linuxea:
    ipam:
     driver: default
     config:
       - subnet: 172.2.0.0/24

现在,你就可以使用静态的ip进行访问

[marksugar@www.linuxea.com /opt/2019/net]# docker-compose -f ./docker-compose.yaml up -d
Creating redis ... done
Creating softether4.27 ... done
[marksugar@www.linuxea.com /opt/2019/net]# docker exec -it softether4.27 sh
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
    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
293: eth0@if294: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP 
    link/ether 02:42:ac:02:00:0b brd ff:ff:ff:ff:ff:ff
    inet 172.2.0.11/24 brd 172.2.0.255 scope global eth0
       valid_lft forever preferred_lft forever

安装redis-client

/ # apk update
fetch http://dl-cdn.alpinelinux.org/alpine/v3.8/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.8/community/x86_64/APKINDEX.tar.gz
v3.8.2-56-g4d33ed061d [http://dl-cdn.alpinelinux.org/alpine/v3.8/main]
v3.8.2-53-g53558ad6fc [http://dl-cdn.alpinelinux.org/alpine/v3.8/community]
OK: 9559 distinct packages available
/ # apk add redis
(1/1) Installing redis (4.0.11-r0)
Executing redis-4.0.11-r0.pre-install
Executing redis-4.0.11-r0.post-install
Executing busybox-1.28.4-r0.trigger
OK: 68 MiB in 35 packages

通过静态ip链接

/ # redis-cli -a OTdmOWI4ZTM4NTY1M2M4OTZh -h 172.2.0.10
Warning: Using a password with '-a' option on the command line interface may not be safe.
172.2.0.10:6379> info
# Server
redis_version:5.0.0
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:a7a8d032c5a69a3f
redis_mode:standalone
os:Linux 4.18.12-1.el7.elrepo.x86_64 x86_64
arch_bits:64
multiplexing_api:epoll
atomicvar_api:atomic-builtin
gcc_version:6.4.0
process_id:10
run_id:cbf1e8eacc74da75c3dfcf797d104a8a2f95076e

使用ipam可以为新网络定义特定的CIDR块,然后将每个容器连接到该网络,可以在该范围上指定其IP地址。

现在,redis始终使用IP地址172.2.0.10运行,softether运行172.2.0.11,并且我能够在配置文件中对这些地址进行硬编码。

延伸阅读#

linuxea:白话容器之虚拟化网络与容器网络(8)

linuxea:白话容器之docker网络(9)

学习更多#

学习如何使用Docker CLI命令,Dockerfile命令,使用这些命令可以帮助你更有效地使用Docker应用程序。查看Docker文档和我的其他帖子以了解更多信息。