前言

最近VPS重启,发现容器间的IP全乱了。这就直接出现一个致命问题,那就是按之前方案Traefik2 根据配置的 Dynamic Configuration找到对应服务时,是要写死services容器IP的。而现在一重启ip全乱了,对应的traefik流量就会乱套,急需为docker每个容器指定固定IP,把容器的IP固定下来。这样下次重启VPS,就不会再出现随机分配IP给容器的情况,也就不会套服务了。

问题描述

举个例子:

假设我给Traefik 配置了 2个router 及都指定了对应service容器的IP,如下图

  • blog1.zctou.com --> http://172.18.0.3 对应 typecho1容器
  • blog2.zctou.com --> http://172.18.0.4 对应 typecho2容器

    利用 Docker-Compose 为每个容器指定固定IP(多个)

    利用 Docker-Compose 为每个容器指定固定IP(多个)

因为之前的配置没有把ip固定下来,那么 http://172.18.0.3 完全有可能分配 typecho2容器,http://172.18.0.4 分配给typecho1容器。

那么,这时你访问blog1.zctou.com给的数据自然就是blog2.zctou.com的数据了,反过来亦然。

若然不能及时发现,那么你的网站排名就别想要了

这时候,要解决这个乱分配ip的问题思路就很简单了。最好就是像家用路由器一样,能给每个容易在创建的时候就把ip固定下来。

利用docker-compose 给容器分配指定ip

Docker-compose 分配静态IP的两种方法

由于先前用traeifk配置都是docker-compose拉起容器,因此这里解决固定ip的问题也是围绕docker-compose配置进行。

而在compose 的yml文件中分给容器固定IP,一般就两种方法

  • 1.利用docker ipam功能在同一yml文件中分配固定IP。
  • 2.先在外部创建一个网络(external),然后在想要用这网络的容器指定IP。

下面以之前的【VPS搭建Typecho代码】为例,分别给出两种方法配置方法。

原来的docker-compose.yml配置如下:
version: '3'

services:
  web:
    image: fukoy/nginx-php-fpm:php7.4
    container_name: server-phpnginx-tp1
    restart: always
    volumes:
      - ./www:/usr/share/nginx/typecho/ #typecho网站文件
      - ./nginx/conf.d:/etc/nginx/conf.d #nginx配置文件夹
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf #nginx配置文件
    depends_on:
      - mysql
    networks:
      - default
      - proxy
      
  mysql:
    image: mariadb
    restart: always
    container_name: server-mysql-tp1
    volumes:
      - ./mysql/data:/var/lib/mysql
      - ./mysql/logs:/var/log/mysql
      - ./mysql/conf:/etc/mysql/conf.d
    env_file:
      - mysql.env
    networks:
      - default

networks:
  default:
  proxy:
    external: true

方法一:利用 docker ipam分配固定IP

这里用到docker ipam就是IP Address Management Driver ,docker官方文档地址如下:

https://docs.docker.com/engine/reference/commandline/network_create/

使用格式:

version: '3'

services:
  web:
    image: fukoy/nginx-php-fpm:php7.4
    container_name: server-phpnginx-tp1
    restart: always
    volumes:
      - ./www:/usr/share/nginx/typecho/ #typecho网站文件
      - ./nginx/conf.d:/etc/nginx/conf.d #nginx配置文件夹
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf #nginx配置文件
    depends_on:
      - mysql
    networks:
      default:
        ipv4_address: 172.18.0.2      
      proxy:
      
  mysql:
    image: mariadb
    restart: always
    container_name: server-mysql-tp1
    volumes:
      - ./mysql/data:/var/lib/mysql
      - ./mysql/logs:/var/log/mysql
      - ./mysql/conf:/etc/mysql/conf.d
    env_file:
      - mysql.env
    networks:
      default:
        ipv4_address: 172.18.0.3  
        
networks:
  proxy:
    external: true 
  default:
    driver: bridge
    ipam:
      config:
        - subnet: 172.19.0.0/16

可以看到用ipam指定一个内部网络,容器用ipv4_address获取固定IP。如下图所示:

利用 Docker-Compose 为每个容器指定固定IP(多个)

但这些IP只能用于这个内部网络容器间相互访问,要打通与其他网络间的访问,至少要有一个容器要同时在两个网络内。

说得更白一点就是:

这样分配的IP,是以这个yml为单位的小局域网,只能是这个yml中创建的容器间能互相访问。

其他yml创建的容器访问不到这网络里面的容器。

利用 Docker-Compose 为每个容器指定固定IP(多个)

只有这样,才能被外部网络访问得到,因此,这种方法并不适合traefik做反代的配置。

方法二:先建外部网络,再给容器分配ip

要明确一点就是,不同容易间想互通,得在同一个网络中

而这里我们流量都交给的traefik处理,用traefik进行反代,traefik接管了宿主机的80和443端口。

因此要成功把流量导入其他独立容器,必须要将service容器与traefik置于同一个网络中,比如我这里创建的proxy网络。

因此,不同的docker-compose.yml,都要有一个容器与traefik处于同一网段中。

这时候最佳的解决办法就是先定义一个外部网络,再分别在每个docker-compose中用ipv4_address固定IP。

2.1 创建外部网络:

#创建proxy网络
docker network create --driver bridge --subnet=172.18.0.0/24 proxy

2.2 在不同的docker-compose指定IP

比如以前配置的v2ray,要分配固定IP,可以修改如下:

version: '3.7'

services:
  v2ray:
    image: alphacodinghub/v2ray-nginx
    expose:
      - 13307
    container_name: v2ray
    volumes:
      - ./www/html:/var/www/html
      - ./nginxconf/conf.d:/etc/nginx/conf.d
      - ./v2rayconf:/etc/v2ray
    restart: always
    networks:
      proxy:
        ipv4_address: 172.18.0.3

networks:
  proxy:
    external: true

而以前配置的typecho blog,两样的分配方式即可。

version: '3'

services:
  web:
    image: fukoy/nginx-php-fpm:php7.4
    container_name: server-phpnginx-tp1
    restart: always
    volumes:
      - ./www:/usr/share/nginx/typecho/ #typecho网站文件
      - ./nginx/conf.d:/etc/nginx/conf.d #nginx配置文件夹
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf #nginx配置文件
    depends_on:
      - mysql
    networks:
      default:
      proxy:
        ipv4_address: 172.18.0.4
      
  mysql:
    image: mariadb
    restart: always
    container_name: server-mysql-tp1
    volumes:
      - ./mysql/data:/var/lib/mysql
      - ./mysql/logs:/var/log/mysql
      - ./mysql/conf:/etc/mysql/conf.d
    env_file:
      - mysql.env
    networks:
      - default

networks:
  default:
  proxy:
    external: true

总结

终上所述,利用docker-compose.yml给容器指定固定IP很简单,直接在networks上指定即可,命令:ipv4_address: 内部ip

    networks:
      default:
      proxy:
        ipv4_address: 172.18.0.4

前提是,这个ip段必须在你在第一步开始之初就创建好的网络中,也就是docker network create --driver bridge --subnet=172.18.0.0/24 proxy内。

这样给不同的容器分配好固定IP,就算VPS自动重启,也不会出错服务不匹配现象了。

踩过的坑:

docker-compose.yml的配置写法有两种 :

  • list
  • mapping

两种写法不可以混用在同一个根目节点下。如:

# 这样的写法是正确的都是一对一对的出现,就是mapping
    networks:
      default:
      proxy:
        ipv4_address: 172.18.0.4
        
# 这样的写法也是对的,就是list
    networks:
      - default
      - proxy
      
# 这样的写法是错的,list与mapping混用了
    networks:
      - default #list 
      proxy: #mapping
        ipv4_address: 172.18.0.4

也就是说

# list 的样式
list:
  - a
  - b

# mapping 的样式 
mapping:
  a:
  b:

翻译成json文件是这样的:

{
   "list": [
      "a",
      "b"
   ],
   "mapping": {
      "a": null,
      "b": null
   }
}

混用会报错。

附docker检查,创建,查看网络相关命令,方便操作:

要想两个容器能互相访问,处在同一网络(docker network)是关键

把握好这点,就能娴熟驾驭不同的docker-compose拉起的容器的连接问题。

  1. 查看docker现有网络: docker network ls
  2. 宿主机中,创建一个网络:docker network create networkName
  3. 将容器连到已经创建的网络中docker network connect networkName containerName
  4. 查看某个网络内有哪些容器:docker network inspect networkName
  5. 使用docker network --help 获得network相关的帮助。
  6. 可以在运行容器时直接指定连接network:docker run --network networkName imageName