将静态IP分配给Docker容器和Docker Compose

2023/05/23

1. 概述

当我们运行Docker容器时,它会使用IP地址连接到虚拟网络,出于这个原因,我们希望服务能够动态获取配置。但是,我们可能希望使用静态IP而不是自动IP分配。

在本教程中,我们将了解内置配置与手动为容器分配IP之间的区别。最后,我们演示一些带有测试的DockerCompose示例。

2. DHCP和DNS

让我们看看使用DHCP和DNS解析主机名称的Docker内置IP分配给容器。

2.1 Docker如何分配IP

Docker首先为每个容器分配一个IP,充当DHCP服务器。此外,还有多个DNS服务器

然后容器使用dockerd中的服务器处理DNS请求,该服务器识别同一内部网络上其他容器的名称。这样,容器可以在不知道其内部IP地址的情况下进行通信。尽管每次应用程序启动时内部IP地址可能不同,但由于dockerd中的内部 DNS服务器,容器仍然可以轻松地使用人类可读的名称进行连接。

然后,dockerd将名称查找发送到CoreDNS(来自CNCF)。最后,请求根据域名移动到主机

域docker.internal有一个副作用,它包括解析为当前主机的有效IP地址的DNS名称host.docker.internal。它允许容器联系那些主机服务,而不必担心硬编码IP地址。虽然不推荐,但它可以方便地用于开发目的。

2.2 网络示例

例如,我们可以为MySQL服务运行一个容器,下面是Docker Compose YAML文件定义:

services:
    db:
        image: mysql:latest
        environment:
            - MYSQL_ROOT_PASSWORD=password
            - MYSQL_ROOT_HOST=localhost
        ports:
            - 3306:3306
        volumes:
            - db:/var/lib/mysql
        networks:
            - network

volumes:
    db:
        driver: local

networks:
    network:
        driver: bridge

像往常一样,我们运行我们的容器:

docker-compose up -d

下面使用jq的格式语法从容器的角度检查网络来获取JSON输出:

docker inspect --format='' 2d3f4c69a213 | jq .

Docker Compose根据当前目录分配网络名称。例如,如果我们在project目录中,我们可以看到类似下面的输出:

{
    "project_network": {
        "IPAMConfig": null,
        "Links": null,
        "Aliases": [
            "project-db-1",
            "db",
            "2d3f4c69a213"
        ],
        "NetworkID": "39ffbd8155d11ba03d0b548307f549f06790fe045e121a6d862b070d4fb67fa7",
        "EndpointID": "0eba235239b06f7e0cb5065b7f2ebd83e7d227f8cfad4df8de73260472737500",
        "Gateway": "172.19.0.1",
        "IPAddress": "172.19.0.2",
        "IPPrefixLen": 16,
        "IPv6Gateway": "",
        "GlobalIPv6Address": "",
        "GlobalIPv6PrefixLen": 0,
        "MacAddress": "02:42:ac:13:00:02",
        "DriverOpts": null
    }
}

容器从网络创建的子网中获取私有172.19.0.2IP地址。

最重要的是,我们可以看到有关IPAMConfig的信息,即IP地址管理。当我们静态分配IP时,这将是相关的

现在,我们可以检查网络:

docker inspect network project_network

这一次,我们对网络有了更好的了解:

[
    {
        "Name": "project_network",
        "Id": "39ffbd8155d11ba03d0b548307f549f06790fe045e121a6d862b070d4fb67fa7",
        "Created": "2022-09-09T16:19:26.27396468+02:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.19.0.0/16",
                    "Gateway": "172.19.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "2d3f4c69a2139dea9089a6d42907fdc085282c5df176b39bf7c20f5d0780179d": {
                "Name": "project-db-1",
                "EndpointID": "7447fe2550afb3f980f36449673724e9ed6dd16f41a085cc20ada3074a0d8e54",
                "MacAddress": "02:42:ac:13:00:02",
                "IPv4Address": "172.19.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {
            "com.docker.compose.network": "network",
            "com.docker.compose.project": "project",
            "com.docker.compose.version": "2.10.2"
        }
    }
]

值得注意的是,Docker Compose网络从版本2开始可用。

3. 静态IP

了解更多有关自动IP分配的信息后,我们现在将创建网络的子网,然后我们可以为我们的服务分配我们喜欢的IP。

3.1 分配静态IP

如果我们使用DockerCLI,首先通过创建子网来实现此目的:

docker network create --subnet=10.5.0.0/16 mynet

然后,我们使用静态IP运行容器,同时使用MySQL服务:

docker run --net mynet --ip 10.5.0.1 -p 3306:3306 --mount source=db,target=/var/lib/mysql -e MYSQL_ROOT_PASSWORD=password mysql:latest

下面是一个使用Docker Compose的完整示例:

services:
    db:
        container_name: mysql_db
        image: mysql:latest
        environment:
            - MYSQL_ROOT_PASSWORD=password
            - MYSQL_ROOT_HOST=10.5.0.1
        ports:
            - 3306:3306
        volumes:
            - db:/var/lib/mysql
            - ./init.sql:/docker-entrypoint-initdb.d/init.sql
        networks:
            network:
                ipv4_address: 10.5.0.5

volumes:
    db:
        driver: local

networks:
    network:
        driver: bridge
        ipam:
            config:
                -   subnet: 10.5.0.0/16
                    gateway: 10.5.0.1

我们已经通过ipam关键字定义了网络的子网,并为服务分配了一个IPv4地址。为了进行更改,我们使用了 10.5.0.5。172.*和 10.*IP地址通常用于专用网络。我们还可以使用IPv6地址,它的地址长度为128位,由于效率更高,它将取代IPv4。

按照建议,我们将网关地址分配给数据库主机MYSQL_ROOT_HOST。

最后,我们添加一个SQL脚本来创建用户、数据库和表:

CREATE DATABASE IF NOT EXISTS test;
CREATE USER 'db_user'@'10.5.0.1' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON *.* TO 'db_user'@'10.5.0.1' WITH GRANT OPTION;
FLUSH PRIVILEGES;

use test;

CREATE TABLE IF NOT EXISTS TEST_TABLE(id int, name varchar(255));

INSERT INTO TEST_TABLE
VALUES (1, 'TEST_1');
INSERT INTO TEST_TABLE
VALUES (2, 'TEST_2');
INSERT INTO TEST_TABLE
VALUES (3, 'TEST_3');

我们希望只允许用户访问该特定地址的数据库。

容器启动后,我们可以使用docker ps查看它的定义:

CONTAINER ID   IMAGE          COMMAND                  CREATED         STATUS         PORTS                                                  NAMES
97812e199512   mysql:latest   "docker-entrypoint.s…"   7 minutes ago   Up 7 minutes   0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060/tcp   mysql_db

现在可以通过输入密码连接到数据库,我们使用容器名称或ID,因为它们解析为DNS的别名:

mysql --host=mysql_db -u db_user -p

现在,使用status命令,我们可以测试我们的MySQL主机是否解析为容器ID:

Connection id:          10
Current database:       test
Current user:           db_user@10.5.0.1
SSL:                    Not in use
Current pager:          stdout
Using outfile:          ''
Using delimiter:        ;
Server:                 MySQL
Server version:         8.0.30 MySQL Community Server - GPL
Protocol version:       10
Connection:             97812e199512 via TCP/IP
Server characterset:    utf8mb4
Db     characterset:    utf8mb4
Client characterset:    utf8mb3
Conn.  characterset:    utf8mb3
TCP port:               3306

3.2 与内置DockerIP管理的区别

让我们检查一下容器,在静态IP的情况下,我们可以看到IPAM配置现在有一个IPv4地址:

{
    "project_network": {
        "IPAMConfig": {
            "IPv4Address": "10.5.0.5"
        },
        "Links": null,
        "Aliases": [
            "project_db",
            "db",
            "122c0c6bfcf9"
        ],
        "NetworkID": "7ac7a1d9e33dffc65bc867aee4db04b9b8fecaeb3bbb91c74c2f72e4611c6955",
        "EndpointID": "84145191a0327b777b6a31bacb2a0260d9a31e8c22cbfca1923775b3649b1d7e",
        "Gateway": "10.5.0.1",
        "IPAddress": "10.5.0.5",
        "IPPrefixLen": 16,
        "IPv6Gateway": "",
        "GlobalIPv6Address": "",
        "GlobalIPv6PrefixLen": 0,
        "MacAddress": "02:42:0a:05:00:05",
        "DriverOpts": null
    }
}

从容器的角度来看,这是主要的区别。如果我们需要一个静态的私有IP地址,我们应该考虑是否需要使用它。大多数时候,我们希望一个静态IP从另一个容器或主机与一个容器通信,Docker的内置网络已经可以处理这个问题。

但是,我们可能希望手动指定一个私有IP地址,例如,用于直接从主机访问容器。

值得注意的是使用Docker Swarm自定义网络的可能性。

4. 总结

在本文中,我们介绍了Docker如何管理IP分配以及如何向容器添加静态地址,并演示了使用或不使用静态IP运行 MySQL服务的Docker Compose配置示例。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章