docker的网络互联中的3个注意点

容器内访问外网

只要宿主机能访问外网,那么天生的容器就能自然而然的访问外网。假如你的容器访问不了外网,那么就需要知道容器能访问外网是怎么做到的,然后再去排查问题。

这是宿主机和容器之间采用了 ip_forward 技术,那么什么是 ip_forward,请看这篇文章:ip_forward与路由转发。不过这篇文章讲得比较深奥,我是没有太看明白,只是明白了这么一点浅显的道理:内网、容器、虚拟机要访问外网,那么在网关(针对内网)、宿主机(针对容器、虚拟机)上要设置 ip_forward。

内网 --> 网关 --> 外网 : 那么需要在网关上设置ip_forward
容器 --> 宿主机 --> 外网 : 那么需要在宿主机上设置ip_forward
虚拟机 --> 宿主机 --> 外网 : 那么需要在宿主机上设置ip_forward

在宿主机上查看 ip_forward 有没有打开的方法:

cat /proc/sys/net/ipv4/ip_forward
或者
sysctl net.ipv4.ip_forward

对于sysctl,可以查看这篇文章:《lsmod,modprobe和sysctl的说明》

在docker安装的时候,安装程序会将 net.ipv4.ip_forward 设置为1,同时安装程序进一步会将 net.ipv4.conf.all.forwarding 也设置为1。
我估计对于虚拟机的安装程序来说,也会有这样的操作。

容器之间的互联

这里只讲在一台机器上的容器之间通信,同时涉及以下情况:
1. 不涉及:参见我以前的博文《docker容器之间通过link方式的互联》
2. 不涉及 docker network create 创建的容器之间的网络
3. 不涉及 overlap 网络,比如 ingress 和 flannel

比如有2个容器,a容器的ip是 172.17.0.2(其中有一个nginx,对外暴露了80端口),b容器的ip是 172.17.0.3 ,那么在 b容器中:
telnet 172.17.0.2 80
OK,没有问题。

所以,在一台机器内的容器,只要2个都属于同一个 虚拟 bridge,比如默认的 docker0,那么通过IP互联没有任何问题。

容器通过宿主机的IP和端口去访问另一个容器对外暴露的端口

上面这句话很绕口,我们来举个例子:
宿主机的IP是 192.168.1.100
a容器内有一个nginx,对外暴露了80端口,比如 docker run -d -p 80:80 nginx
在b容器内进行:

ping 192.168.1.100  #OK,没有问题,说明能访问到宿主机
telnet 192.168.1.100 80 #不行了,说明能访问到宿主机,但是因为宿主机的防火墙给挡住了

那么我们修改一下宿主机的iptables,让来自 docker0 这个虚拟网卡的流量能通行:
iptables -I INPUT chainnumber -i docker0 -j ACCEPT
以后修改iptables尽量不要用 iptables -A,因为 iptables 的策略中有可能最后一条是 reject all,所以必须要插在 reject all 这一条之前。
那么上面中的 chainnumber 用 iptables -L -n –line-numbers 来查看,具体大家可以去查互联网资料。

修改后iptables后,这样就可以在b容器访问宿主机的某个容器暴露端口了。

基于docker的gitlab安装、设置和内存优化

基于docker的gitlab安装,大家一定要看一下官方文档: https://docs.gitlab.com/omnibus/docker/

我的这个gitlab安装有一些约束条件:
1. 没有域名,只有IP
2. 服务器内存只有4G
3. 内部人员协同开发,无需使用电子邮件
4. 仅仅用于最基本的git协同开发,没有使用auto devops
5. 没有采用 https,只暴露80端口,其他的22端口也无需暴露
6. 没有采用 Prometheus
7. 采用 docker-compose 来安装

docker-compose.yml 配置如下:

 gitlab:
   image: 'gitlab/gitlab-ce:latest'
   restart: always
   container_name: gitlab
   environment:
     - GITLAB_OMNIBUS_CONFIG=external_url 'http://192.168.1.100'
     - TZ=Asia/Shanghai
   ports:
     - '80:80'
   volumes:
     - '/srv/gitlab/config:/etc/gitlab'
     - '/srv/gitlab/logs:/var/log/gitlab'
     - '/srv/gitlab/data:/var/opt/gitlab'

这个配置文件解释一下:
TZ=Asia/Shanghai详见这篇文章:设定docker容器时间的3种方法

通过 docker-compose up -d gitlab 起来后,别忘记了有一个安全问题,仅内部访问,限制外界访问,详见这篇文章:Docker中限制网络访问中的ext_if是什么?

gitlab起来后,登录 root 的账号,登录后,进行应用管理设置,其中关闭 auto devops 这个功能:
Admin area –> Settings –> CI/CD –> Default to Auto DevOps pipeline for all projects 前面的勾去掉。

修改 /srv/gitlab/config/gitlab.rb,将 prometheus 禁用:
prometheus_monitoring[‘enable’] = false

内存优化

上述过程做好后,过了一天,发现gitlab运行相当慢,一看服务器内存已经没有了,而且使用了大量的swap,于是上网查如何内存优化 gitlab,找到了这篇讲得很全面的文章:解决GitLab内存消耗大的问题。最终我的优化如下:

unicorn['enable'] = true
unicorn['worker_timeout'] = 60
unicorn['worker_processes'] = 2

unicorn['worker_memory_limit_min'] = "300 * 1 << 20"
unicorn['worker_memory_limit_max'] = "500 * 1 << 20"

sidekiq['concurrency'] = 16

postgresql['shared_buffers'] = "256MB"
postgresql['max_worker_processes'] = 8

重启后,观察了2天,内存始终消耗为 1.5-2 G之间。


上述的内容已经有些过时了,最新的gitlab有些不同了,这里做一些补充

  • 假如安装的时候,外网不能用80端口,那么docker-compose配置文件应该这么设定:
 gitlab:
   image: 'gitlab/gitlab-ce:latest'
   restart: always
   container_name: gitlab
   environment:
     - GITLAB_OMNIBUS_CONFIG=external_url 'http://192.168.1.100:8000'
     - TZ=Asia/Shanghai
   ports:
     - '8000:8000'
   volumes:
     - '/etc/gitlab/config:/etc/gitlab'
     - '/etc/gitlab/logs:/var/log/gitlab'
     - '/etc/gitlab/data:/var/opt/gitlab'
  • 内存优化已经改了,成了:

puma[‘enable’] = true
puma[‘worker_timeout’] = 60
puma[‘worker_processes’] = 2
puma[‘max_threads’] = 4
puma[‘per_worker_max_memory_mb’] = 1024
sidekiq[‘max_concurrency’] = 16
postgresql[‘shared_buffers’] = “256MB”
postgresql[‘max_worker_processes’] = 8

  • 安装好后,初次登录,居然不能指定root的密码了,需要参照官方文档来进行设定:https://docs.gitlab.com/ee/security/reset_user_password.html ,但是这个文档只是涉及在服务器上传统安装gitlab,那么在docker中该如何设定呢?

先进入容器内,docker exec -it gitlab /bin/bash,然后执行 gitlab-rake “gitlab:password:reset”,意外的事情发生了,居然久久没有反应,感觉像死掉一样,过了大概5分钟,才会出来一些提示,在这个5分钟内,假如你等不及敲了一些回车,对不起,这个过程还要重新再来。所以记住:这个过程会很长,不要着急,千万不要敲回车。(这一段是修改密码的,现在的gitlab有设定初始密码的地方,见下面)

  • Visit the GitLab URL, and log in with username root and the password from the following command:

docker exec -it gitlab grep ‘Password:’ /etc/gitlab/initial_root_password

Dockerfile中的COPY中的context(上下文)指得是什么?

Dockerfile中的COPY作用、格式的详细描述不是本文所要涉及的。本文只是聊一下COPY时候要注意的context。

我们先来简单看一下COPY的第一种格式,第二种格式不说了:

COPY <src>... <dest>

dest: 指得是镜像(image)中的路径
src: 指得是Dockerfile所在的context路径
那么COPY的作用就是:将构建镜像的电脑上的文件或者文件夹拷贝到镜像中去。

我们知道,Dockerfile文件往往是放在github上的某个工程里,当我们checkout工程,然后执行 docker build 命令的时候,我们的电脑和作者的电脑肯定不一样的,所以,明白了这一点,基于这样最基本的常识,我们马上就能明白什么样的src是正确的,什么样的src是错误的:

正确:
COPY ./package.json /app/
COPY package.json /usr/src/app/

错误:
COPY ../package.json /app 因为上一层目录大家都不一样的
或者 COPY /opt/xxxx /app 有可能连操作系统都不一样,哪里有什么/opt这样的目录

所以,Dockerfile中的COPY中的context(上下文)指得就是 Dockerfile 所在的工程中的目录结构,离开了这个工程的其他目录都是错误的。

顺便说一下:dest 是可以在镜像中的任意位置的,这个无所谓,不用解释大家都明白了吧。

参考资料

dockerfile文件中copy上下文目录

Docker中限制网络访问中的ext_if是什么?

我们往往通过 -p 来使得外界访问docker中的容器,比如:
docker run -p 80:80 some-app-image

但是有下面几个场景需要知道:
1,其他容器想要访问这个容器的端口,外界不允许访问,这个容易,用docker的network既可以实现。
2,为了安全问题,只允许我所知道的外界来访问这个端口,除此以外我都不允许访问。

第2个场景经常出现,比如某服务器,有外网网卡,其中某个内网应用,通过上面的 -p 开放会使得外网也能访问。对此问题,我记得最早时候的docker是没有提到这个问题的(或者在某个地方我没有注意到),为此我还专门到网上去查找资料和询问别人,别人和我说需要对iptables进行设置。

后来,我再看docker官方文档的时候,发现了Restrict connections to the Docker daemon,限制连接访问docker,文档中举了几个例子,我们来看一下这个例子:

$ iptables -I DOCKER-USER -i ext_if ! -s 192.168.1.1 -j DROP
-I DOCKER-USER: 表示插入用户自定义的DOCKER-USER的chain中
-i ext_if: 针对 ext_if 网卡
! -s 192.168.1.1 -j DROP: 除了来源是 192.168.1.1 以外的地址全部不能访问

那 ext_if 是什么网卡?难道是 docker 又弄出来的?到网上查了一下,到处没有说起这个 ext_if 是什么,直到我最终在docker的官方文档:where ext_if is the name of the interface providing external connectivity to the host.中找到这样的话才明白,原来这个 ext_if 就是docker daemon所在服务器的网卡名称,也就是说换成比如 eth0 这样的即可。

这个让我想起了一个故事:
微软的很多安装,有时候会蹦出这样的一段话:“请按任意键继续”,于是很多人就打电话去问微软的客服:“请问任意键在键盘中的哪个位置,我怎么找不到这个键啊?”,后来微软为了解决这个歧义,把这句话改成了:“请按空格键继续”。

那么docker文档中的这句话也出现了歧义:iptables -I DOCKER-USER -i ext_if ! -s 192.168.1.1 -j DROP,应该改成这样就好了,否则找了一大圈最后才发现原来出现歧义了:iptables -I DOCKER-USER -i “your_extern_interface” ! -s 192.168.1.1 -j DROP,并且用双引号括起来,这样一来每个人都能明白了。

如何对alpine设置时区为Asia/Shanghai

alpine是个非常简单的docker镜像,沿用以前的修改容器时间的方法都不能起效,因为它去掉了很多功能,其中就去掉了timezone这个功能,默认就是UTC时间。

我们进入alpine容器内看看里面的内容:
docker exec -it –rm alpine /bin/sh
> date
显示的是UTC时间,和北京时间相差8小时
> ls /usr/share/zoneinfo/
提示目录不存在

所以,我们需要对alpine补全这个timezone的功能,如此才能起效,Dockfile的内容如下:
FROM alpine
RUN apk add –no-cache tzdata
ENV TZ=Asia/Shanghai

为什么要安装tzdata这个包,见下面的参考资料《Linux – 时区和时间调整方法》,讲得很好。

参考资料

Docker Alpine 镜像设置东八区
https://github.com/gliderlabs/docker-alpine/issues/136
Linux – 时区和时间调整方法

用showdoc来编写网关接口文档

最近在工作上做了一个网关对外暴露接口,做好后要需要提供接口的文档,然后上网去查找有没有好用的编写接口文档的工具。

接口文档的编写分在线、自己部署2种。在线的网上大家可以搜一下,好多都是提供几个免费的文档,多了后需要付费。自己部署的分为:免费、开源、商业的。这里有一篇文章罗列了一些工具网站:接口文档工具,我就是从这里找到了 showdoc 这个比较满意的工具。

showdoc的官网地址:https://www.showdoc.cc/ 里面有一个示例工程,可以看到一些最终的效果;同时假如你不想搭建一套私有的,你可以进行注册后创建项目,同时拉人进来查看文档。

如何搭建一套showdoc的应用,showdoc的帮助文档写得比较仔细,我因为偷懒,采用了他的docker来进行安装,安装的时候,因为没有严格按照他的步骤,导致了出现一些文件写权限的问题,注意:必须要严格按照文档上的步骤来进行部署 docker 。

部署好首次登录,用户名和密码为:showdoc/123456,进去后别忘记修改密码。

我这里大致罗列一下功能点,这些功能点对于接口文档的编写挺有用的,说明作者是用心了:
1,左侧可以做成树形结构,可以用单独的页面来描述全局性的说明、流程等,用个目录来包含业务接口的页面。
2,有人员权限的划分,基本上对于我们来说,编写、查看这样简单的权限足矣。
3,页面采用markdown,基本上可以满足各种格式的书写;作者特别有心,考虑到一些人对markdown的语法不熟悉,就弄了一些常用的模板,很贴心。

对于其他的一些功能,比如接口测试这样的功能,我个人觉得倒没有这个必要了,术业有专攻,可以用postman来测试的,没有必要这里那么复杂的再实现一个。

最后,提醒一下用 showdoc 的人,别忘记备份了,docker 的备份是 /showdoc_data 这个目录。

在docker中搭建redis集群以及哨兵sentinel

在上一篇文章《在一台centos7服务器上搭建redis集群以及哨兵sentinel》中,我们提到了一个很重要的知识点,大家可以去看看。
在这里,我们会反复提到这个知识点,对于docker下部署redis sentinel非常重要。

部署环境

  1. 假设该宿主机服务器的ip是:192.168.1.100,docker0的ip是:172.17.0.1
  2. 使用redis的镜像是:docker pull redis:5.0.4
  3. redis只搭建1主1从(网上很多都是1主2从,没有这个必要),3个sentinel(3个是必须的,否则在failover的时候就会出现一些不必要的问题)
  4. 6379:redis server,docker的volume目录在 /data/redis-6379
    6380: redis server,docker的volume目录在 /data/redis-6380
    26379:redis sentinel,docker的volume目录在 /data/redis-26379
    26380:redis sentinel,docker的volume目录在 /data/redis-26380
    26381:redis sentinel,docker的volume目录在 /data/redis-26381

部署过程

部署redis-6379

1,mkdir -p /data/redis-6379/conf && mkdir -p /data/redis-6379/data && chmod 666 -r /data/redis-6379 && cd /data/redis-6379/conf
这里的chmod 666 非常重要,原因有下面2个:
我们从redis Dockerfile中可以看到,docker中的redis用户和用户组是 redis,

RUN mkdir /data && chown redis:redis /data

redis在进行sentinel,failover的时候会改写配置文件,redis的用户是redis,而宿主机的目录是另外的人,所以需要改变目录权限
2,vi redis.conf,内容如下:
requirepass “123456”
masterauth “123456”
slave-announce-ip “192.168.1.100”
slave-announce-port 6379
重点来了,这里的masterauth,已经从上一篇文章中解释了。
还有2个配置:slave-announce-ip,slave-announce-port这2个呢?原因和masterauth一样的,当发生failover的时候,这个原先是主的redis就会成为从,所以这2个配置是给主在成为从的时候使用的。但是为什么要在docker环境中用这2个配置,在上一篇文章中为什么不用呢?因为在docker环境下,假如不用这2个配置,那么在连接到主服务器上后,ip地址会变成172.17.0.1这个docker0地址,同时端口始终是6379。等下我们可以看看。
3,加入 docker-compose.yml 以下内容

redis-6379:
  image: redis:5.0.4
  container_name: redis-6379
  volumes:
    - /data/redis-6379/conf/redis.conf:/usr/local/etc/redis/redis.conf
    - /data/redis-6379/data:/data
  ports:
    - 6379:6379
  command: redis-server /usr/local/etc/redis/redis.conf

4,运行docker-compose
docker-compose up -d redis-6379
5,登录redis控制台
docker exec -it redis-6379 /bin/bash
redis-cli
auth 123456
info
可以看到role: master
exit
exit

部署redis-6380

1,mkdir -p /data/redis-6380/conf && mkdir -p /data/redis-6380/data && chmod 666 -r /data/redis-6380 && cd /data/redis-6380/conf
2,vi redis.conf,内容如下:
requirepass “123456”
masterauth “123456”
slaveof 192.168.1.100 6379
slave-announce-ip “192.168.1.100”
slave-announce-port 6380
3,加入 docker-compose.yml 以下内容

redis-6380:
  image: redis:5.0.4
  container_name: redis-6380
  volumes:
    - /data/redis-6380/conf/redis.conf:/usr/local/etc/redis/redis.conf
    - /data/redis-6380/data:/data
  ports:
    - 6380:6379
  command: redis-server /usr/local/etc/redis/redis.conf

4,运行docker-compose
docker-compose up -d redis-6380
5,登录redis控制台
docker exec -it redis-6379 /bin/bash #注意这里是登录6379这个主redis容器
redis-cli
auth 123456
info
# Replication
role:master
connected_slaves:1
slave0:ip=192.168.1.100,port=6380,state=online,offset=561,lag=1
这里可以看到 slave的ip是正确的。假如我们在 6380 的配置中没有设置slave-announce-ip “192.168.1.100” 和 slave-announce-port 6380的话,那么将在这里看到的是 slave0:ip=172.17.0.1,port=6379,state=online,offset=561,lag=1,如此的设置当然会在failover的时候发生异常了。所以从这里能很清楚明白这2个配置的重要性。
exit
exit

部署redis-26379

1,mkdir -p /data/redis-26379/conf && mkdir -p /data/redis-26379/data && chmod 666 -r /data/redis-26379 && cd /data/redis-26379/conf
2,vi sentinel.conf,内容如下:
protected-mode no
port 26379
sentinel monitor mymaster 192.168.1.100 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 10000
sentinel auth-pass mymaster 123456
sentinel announce-ip “192.168.1.100”
sentinel announce-port 26379
这里的2个配置 sentinel announce-ip 和 sentinel announce-port 和 上面的那2个意思大致差不多,假如不这么设置的话,那么在 sentinel 的ip通信中就会出现 172.17.0.1:26379 这样错误的值。下面会进一步说到。
3,加入 docker-compose.yml 以下内容

redis-26379:
  image: redis:5.0.4
  container_name: redis-26379
  volumes:
    - /data/redis-26379/conf/sentinel.conf:/usr/local/etc/redis/sentinel.conf
    - /data/redis-26379/data:/data
  ports:
    - 26379:6379
  command: redis-server /usr/local/etc/redis/sentinel.conf

4,运行docker-compose
docker-compose up -d redis-26379

部署redis-26380

1,mkdir -p /data/redis-26380/conf && mkdir -p /data/redis-26380/data && chmod 666 -r /data/redis-26380 && cd /data/redis-26380/conf
2,vi sentinel.conf,内容如下:
protected-mode no
port 26379
sentinel monitor mymaster 192.168.1.100 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 10000
sentinel auth-pass mymaster 123456
sentinel announce-ip “192.168.1.100”
sentinel announce-port 26380
3,加入 docker-compose.yml 以下内容

redis-26380:
  image: redis:5.0.4
  container_name: redis-26380
  volumes:
    - /data/redis-26380/conf/sentinel.conf:/usr/local/etc/redis/sentinel.conf
    - /data/redis-26380/data:/data
  ports:
    - 26380:6379
  command: redis-server /usr/local/etc/redis/sentinel.conf

4,运行docker-compose
docker-compose up -d redis-26380
5,cat /data/redis-26379/conf/sentinel.conf #注意这里查看 redis-26379
# Generated by CONFIG REWRITE
sentinel known-slave mymaster 192.168.1.100 6380
sentinel known-sentinel mymaster 192.168.1.100 26380 75f969e584d6cae9f23a6f9da6069d8c69873452
从这里能注意到2件事情:
a,看到“Generated by CONFIG REWRITE”这句注释,说明redis对配置文件进行了修改。这就是为什么我们一开始就把配置文件所在的volumn的权限设置为666的原因:chmod 666 -r /data/redis-xxx,假如上面没有修改,那么就会报错:CONFIG REWRITE failed: Permission denied,这个错误不影响启动,但是会影响发生failover后的一些问题,所以一定要事先设置好权限。
b,看到 192.168.1.100 26380 这个配置,这就是前面所讲到的 sentinel announce-ip, sentinel announce-port 的作用,假如没有设置,那么这里将是 172.17.0.1 26379 这个错误的ip。
cat /data/redis-26380/conf/sentinel.conf,也能看到如此的情况,不多说

部署redis-26381

这里不多说,和 redis-26380类似。

测试

咱们用 https://github.com/champbay/redis-sentinel 这个工程来进行测试
启动这个工程后,可以看到不断的往 redis 中插入 test-i 这样的值
然后,docker stop redis-6379,马上可以看到这个工程开始报错,大概过了5秒钟(因为我上面设置的是5秒钟),又恢复正常了,说明 redis sentinel已经起效了。
我们查看一下 6380 的配置文件,cat /data/redis-6380/conf/redis.conf,看到 slaveof 已经不见了,间接说明成为了主。通过进入redis-6380容器,redis-cli看一下info,可以看到确实成为了master。这里也说明需要事先修改一下配置文件的权限,否则这里就会报错。
我们再次启动 docker start redis-6379,稍等一会,查看6379的配置文件,发现最后多了一行 slaveof 192.168.1.100 6380,(slaveof == replicaof),说明已经成为了从,从 redis-cli也能看出。

docker容器之间通过link方式的互联

这里只是描述在单docker容器之间通过link方式的互联,容器之间的其他联系方式不在本文里面描述。

首先我们来看一个例子:
1. 创建一个mysql容器:
docker run –name mymysql -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql:5.5.49
这里创建了一个名字是 mymysql 的容器,为了举例,故意把名字弄成不一样,同时只是为了举例,不用去计较volumes
2. 创建一个需要使用mysql的容器,这里用wordpress来举例:
docker run –name wordpress -d -p 80:80 –link mymysql:mysql wordpress
同样只是为了举例,不要去计较volumes。很神奇的,不用设置mysql的连接地址,不用设置mysql的密码,wordpress启动后,这些都识别出来了。

这里面到底发生了什么?下面让我们一点一点来分析一下:
我们把mymysql这个容器叫做“源容器”,把wordpress这个容器叫做“接收容器”。在“接收容器”创建的时候,用 –link mymysql:mysql,其中 mymysql是“源容器”的名称,mysql这个名称叫做“别名”,是在“接收容器”中会用到,比如在程序中可以用 jdbc:mysql://mysql:3306/xxdb。

为了让“接收容器”访问“源容器”,比如为了能使得jdbc:mysql://mysql:3306/xxdb找到真正的地址,比如jdbc:mysql://192.168.0.3:3306/xxdb, docker提供了2种方式:
1. 环境变量
2. /etc/hosts 文件

环境变量

我们先运行下面的命令:
docker run -i -t –rm –link mymysql:mysql busybox:latest env #这个意思是在“接收容器”busybox中打印出所有的环境变量

HOSTNAME=5878f7496b83
TERM=xterm
MYSQL_PORT=tcp://192.168.0.3:3306
MYSQL_PORT_3306_TCP=tcp://192.168.0.3:3306
MYSQL_PORT_3306_TCP_ADDR=192.168.0.3
MYSQL_PORT_3306_TCP_PORT=3306
MYSQL_PORT_3306_TCP_PROTO=tcp
MYSQL_NAME=/inspiring_swanson/mysql
MYSQL_ENV_MYSQL_ROOT_PASSWORD=123456
MYSQL_ENV_GOSU_VERSION=1.7
MYSQL_ENV_MYSQL_MAJOR=5.5
MYSQL_ENV_MYSQL_VERSION=5.5.49
HOME=/root

看到了“接收容器”中的打印的环境变量就知道了,“接收容器”将“源容器”中的环境变量加了一个“别名”ENV这样的前缀,我们只要在程序中获取这些环境变量的值,就可以知道我们所需要的。

/etc/hosts 文件

我们先运行下面的命令:
docker run -i -t –rm –link mymysql:mysql busybox:latest cat /etc/hosts #这个意思是在“接收容器”busybox中打印出/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
192.168.0.3     mysql 1023833075ea mymysql
192.168.0.5     6761e72cb33f

看到上面的192.168.0.3 mysql的映射关系了,这样也就能理解为什么jdbc:mysql://mysql:3306/xxdb能找到真正的地址,比如jdbc:mysql://192.168.0.3:3306/xxdb

参考

docker从入门到实践(容器互联)
以Tomcat+Mysql为例,实现Docker多容器连接