Docker系列 了解Docker Compose的配置文件
本文最后更新于 281 天前,如有失效请评论区留言。

本博客由ZGOCLOUD大力赞助!如何更快地访问本站?有需要可加电报群获得更多帮助。本博客用什么VPS?创作不易,请支持苯苯!推荐购买本博客的VIP喔,10元/年即可畅享所有VIP专属内容!

日志

  • 2023-04-18:优化image的latest标签的相关说明。
  • 2023-04-15:新增关于端口号和tag号的提示。

前言

docker run转化为compose:composerize

最近为了解决OpenClash规避PT下载器流量的问题,我尝试了docker的macvlan网络模式。在那个时候,我忽然发现,自己虽然在Docker系列教程里广泛使用docker compose,但似乎从未系统地讲解过为什么推荐使用docker compose; docker compose和一般的docker run有什么区别docker compose有哪些常用模块;等等。所以这里临时补充一期相关内容,带大家了解docker-compose的设计逻辑和基本结构。当然,本章只是讲一些docker-compose较为常见的用法;更加详细的教程请参考官方文档

实例1

这里我会展示一个简单的docker-compose实例。一般,我们都是在某个文件夹里建立一个名为docker-compose.yml的文件,加入类似下面的内容:

version: '3'
services:
  app:
    image: 'jc21/nginx-proxy-manager:latest'
    restart: unless-stopped
    ports:
      - '80:80'
      - '81:81'
      - '443:443'
    volumes:
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt

其实这个文件名可以随意指定;不过,如果你使用了其它名字,你需要用-f--file特别指定某个配置文件。另外,眼尖的小伙伴可能发现,这就是我在《Docker系列 两大神器NPM和ddns go的安装》提供的Nginx Proxy Manager的docker-compose安装脚本。那么,里面的内容是什么意思呢?

首先,我们要先理清docker-compose的层级结构。对于上面的代码,层次关系大致如下图所示:

Typora_zXqvhqkUc6

为了方便书写和观看,一般后一层级比前一层级后退2个空白字符或1个tab。Docker compose对于该格式的要求是挺严格的,有点不对就容易报错。

  • 第1级中,versionservices是同一级参数。这个version只是为了方便帮助我们记录这个docker-compose.yml是什么版本,并不参与docker的实际进程。services就是为了表明我们要开启一个服务。某个Service和它所在的network等组成的整体通常被称为Stack;一般来说,Stack可由1个或多个Service组成 ,而一个Service可由1个或多个Container(即容器)组成。有点套娃的感觉了(ฅ´ω`ฅ)

  • 第2级的app其实是可以随便取的,你取dogcat之类的都无所谓;但不同的Container不可以重复。它是Container在这个stack中的“小名”;在这个局域网中,如果你想和该Container进行数据交换,可以使用“小名”代替IP作为唯一ID。我自己的习惯是,这个Service里的核心应用称为app;数据库称为db;缓存称为cache;其它应用则另外约定。这样的好处是,只看“小名”就可以大致猜到某Container在该Service中的基本角色。如果你看过我的其它Docker教程,基本上都遵循了这个习惯。

  • 第3级定义了该Container的主要参数,比如使用哪个image;重启策略(restart);端口映射(ports;从Container到Host);目录映射(volumes;从Container到Host);等等。

  • 第4级定义了某Container参数的具体设置,比如要映射哪个端口或目录;等等。

值得注意的是,类似80:80的映射是一种很常用的端口映射关系,代表宿主端口:容器端口。一般容器端口是由image定义好的,不能修改;但宿主端口则可以随心所欲,只要不与现用端口冲突即可。

另外,jc21/nginx-proxy-manager:latest后加:latest是一种比较“偷懒”的做法——我不知道用哪个tag,你给爷来个最新版。实际上,从日后迁移的角度看,这并不是一种推荐的做法。最好的做法是你切实知道需要用哪个版本,然后填入具体的tag号,比如jc21/nginx-proxy-manager:github-pr-2816jc21/nginx-proxy-manager:2.10.2之类的。你可以在dockerhub里面查看某个image的最新tag号,比如NPM的就在这里。结合RSShub,你甚至可以直接订阅docker image的tag更新:

msedge_5foFvamChY

这样我们就可以实时了解该image的状态并决定是否更新至某个版本。这也是我不建议大家使用:latest或不加tag号(默认使用latest)的原因之一——它们不利于进行迁移操作(不同时间的latest实际上代表不同的版本)。另外,知道tag号还有利于提高image复用率。比如,缓存(如redis)、数据库(如mysql)等image经常被多个stack共用,使用不一致的tag号会重复下载类似的image,从而增加image相关的容量负荷。这在tag号规范管理的前提下是可以很大程度上避免的。这种感觉在你迁移/升级nextcloud时将会特别明显。

不过,有某些情况下是推荐使用:latest

  • WordPress是建议使用:latest标签的,因为wordpress更新可以在后台进行,不需要特别去升级某个镜像。
  • 需要/有必要频繁更新的镜像,比如diygod/rsshub:chromium-bundledKerwin1202/chatgpt-web。带有:latest标签的docker image更新很简单,只需要:
docker-compose down # 下线
docker-compose pull # 拉取latest镜像
docker-compose up -d # 重新上线

从机器的角度上看,除了网络层,docker-compose.ymldocker run之类的命令并没有本质区别的。上面的docker-compose.yml可以部分地简化成下面的Linux命令:

docker run \
    -p 80:80 \
    -p 81:81 \
    -p 443:443 \
    -v <工作目录>/data:/data \
    -v <工作目录>/letsencrypt:/etc/letsencrypt \
    jc21/nginx-proxy-manager:latest

总之,这个简单的例子表明:docker-compose.yml实际上是将docker命令里的严格等级关系进行良好的可视化。具体来说,就是使用者很容易看清它所表达的内涵;甚至Stack里有很多Container时,其中的层级关系也不难看清。运行一个复杂Stack的Linux命令是比较难写的,也不够直观。

实例2

这里我们看一个更加复杂的docker-compose.yml,它取自我在《Docker系列 WordPress系列 搭建WordPress个人博客》中安装wordpress的脚本:

version: '3.0'

services:
  db:
    # arm的机器, mysql:5.7请改成mysql:oracle
    image: mysql:5.7
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: rootpassword # 按需修改
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: yourpassword # 按需修改
    volumes:
      - './db:/var/lib/mysql'
    networks:
      - default

  app:
    image: wordpress:latest
    restart: unless-stopped
    ports:
      - 4145:80  # 按需修改。与防火墙开放端口一致。
    environment:
      WORDPRESS_DB_Host: db
      WORDPRESS_DB_NAME: wordpress
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: yourpassword # 按需修改
    volumes:
      - './app:/var/www/html'
    links:
      - db:db
    depends_on:
      - redis
    networks:
      - default

  redis:
    image: redis:alpine
    restart: always
    volumes:
      - ./redis-data:/data
    networks:
      - default

networks:
  default:
    name: wordpress

首先,这个脚本多了一些#标记的文字。和大多数编程语言一样,它们是注释,在实际运行时并不生效,一般的作用就是给读者提供一些重要信息,或传达脚本作者的某些思想。

在这里,Services里有3个Container的“小名”,分别叫appdbredis。它们分别对应wordpress、mysql和redis缓存等应用。

app这个应用里,我们还可以看到一些特别的Container参数:

links:
  - db:db
depends_on:
  - redis

这个links - db:db的表达提示app这个应用会使用dbdepends_on: redis表明redis要在app之前启动且在之后停止。因此,虽然linksdepends_on两者均涉及依赖关系,但 depends_on 主要表明应用必须启动和停止的顺序,而links 表明容器间存在网络通信。不过,实际使用中我感觉差别不大,有深入使用体会的小伙伴可以评论区留言。

我们还看到一个新的第1级变量networks。其实它一直是存在的。在实例1中,它是一个隐藏变量,但却实际生效。我们再仔细地观察各应用与network的关系:

version: '3.0'

services:
  db:
    ...
    networks:
      - default

  app:
    ...
    networks:
      - default

  redis:
    ...
    networks:
      - default

networks:
  default:
    name: wordpress

这里其实就是说:db/app/redis均使用一个叫default的本地网络,它在docker networks中的名字为wordpress。这是一种比较简单的情况,也是我比较常用的写法,它可以帮助我对某个Stack的个性化网络起个好听的名字。实际上,这里networks是可以不同的;一个Stack可以同时使用1个或多个network。

总之,从本实例中,我们可以感受:

  • docker-compose的应用前后端关系十分清晰明了
  • docker-compose可以定义复杂的网络结构和依赖关系

实例3

实例1和实例2均会产生一个独特的Stack局域网。一般来说,在docker networks中,不同的Stack网段可以通过172.17.0.1通讯。这个地址类似于路由器中的网关地址192.168.1.1一样——172.17.0.1是docker networks的网关(可能理解不太准确 (ฅ´ω`ฅ))。这也是我在使用Nginx Proxy Manager反代相邻docker应用时,喜欢反代http://172.17.0.1:端口地址的原因,因为这样IP地址一致比较好记,我只要关注某个应用的端口号就行;而且可以避免搜索更高级别的网络,也是一种更加安全、纯粹和高效的用法。

不过,其实Stack网络可以更具有侵略性——直接给Stack分配一个和宿主机同网段的IP!据我所知,macvlan就是其中一种可靠的方式。

比如,我新建一个网络:

docker network create -d macvlan \
  --subnet=192.168.1.0/24 \
  --gateway=192.168.1.1 \
  -o parent=ens18 \
  PTnetwork

其中,--subnet指定了一个CIDR地址192.168.1.0/24,它对应的是一个长度为256的网段(查询网址):

msedge_FGB7PFXd7i

--gateway指定了这个网段的网关地址,即192.168.1.1。其实这个网段/网关对应的就是最常用的家庭网络地址方案。-o parent=ens18对应的本机的网卡。PTnetwork是我为这个docker network起的名字,意为“给PT应用提供的网络”。具体的设置我暂不展开。

总之,上述命令的意思是:建立一个名为PTnetwork的docker network,它在ens18网卡的192.168.1.0/24子网中,网关为192.168.1.1。如果你的宿主机/家庭局域网也是使用这个网段,这个docker network就顺利地“寄生”到了家庭局域网中;而这个docker network的Container就变成了一台台具有独立IP的局域网“设备”。因此,路由器的DHCP服务会给他们分配IP;不过一般我们都是使用静态IP的方式,即直接指定某Container的IP。

这里是一个我正在使用的transmission的docker-compose.yml脚本:

---
version: "2.1"
services:
  transmission:
    image: chisbread/transmission:version-3.00-r13.1
    restart: unless-stopped
    Container_name: transmission
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Asia/Shanghai
      - USER=11 #optional
      - PASS=1111111111 #optional
    volumes:
      - ./config:/config
      - ./downloads:/downloads
      - ./watch:/watch
    ports:
      - 9091:9091
      - 51413:51413
      - 51413:51413/udp
    networks:
      macvlan_network:
        ipv4_address: 192.168.1.167 # 同网段IP

networks:
  macvlan_network:
      name: PTnetwork # 之前搞好的IP 

我们关注其中和网络有关的命令:

    ...
    networks:
      macvlan_network:
        ipv4_address: 192.168.1.167 # 同网段IP

networks:
  macvlan_network:
      name: PTnetwork # 之前搞好的IP 

脚本意思是:该Container(名为transmission)使用一个叫macvlan_network的网络,并使用192.168.1.167作为transmission的静态IPv4地址。macvlan_network是一个存在于Stack外部(external)的docker networks,在那里它的名字叫PTnetwork

这样,在实际使用时,transmission就会拥有一个独立的局域网IP。这在设置OpenClash的网络时具有额外好处,因为OpenClash Fake-IP模式下,如果使用防火墙转发的方法进行本地DNS劫持,则可以设置不走代理的局域网设备IP。如些一来,transimission下载/上传PT的流量(往往是恐怖的大)就不会损耗节点流量(往往是有限的少),从而达到规避PT流量的目的。

据我所知,这种网络模式还常见于某些容器云的部署中,它们往往要求多个类似的容器云,但又要求有独立的局域网IP。

小结

Docker compose浅易易懂,但却可以支持非常复杂的功能。利用Docker compose可以使docker布署变得简单而强大。本文所描述的特性也是我系列教程里都十分推崇docker-compose的主要原因

docker-compose.yml就像是一个严格记录和执行你曾经执行过的docker run等操作;而你想要再次执行的时候,只需要简单的docker-compose up -d。因为只是一个文本文件,想要备份它也是轻而易举。我以前也是用docker run或后台GUI之类的方式使用docker的。但是深刻了解docker-compose后,我基本已经抛弃了那些旧方式,它们实在是弱爆了!

如果你是docker初学者,看完本文后,希望你也可以尽快过渡到docker-compose的使用喔!难度应该不大的,跨过去就行了!

扩展阅读

---------------
完结,撒花!如果您点一下广告,可以养活苯苯😍😍😍

感谢ZGOCLOUD友情赞助 (ฅ´ω`ฅ) 本博客基于m2w创作。版权声明:除特殊说明,博客文章均为Bensz原创,依据CC BY-SA 4.0许可证进行授权,转载请附上出处链接及本声明。VIP内容严禁转载!由于可能会成为AI模型(如chatGPT)的训练样本,本博客禁止将AI自动生成内容作为文章上传(特别声明时除外)。如有需要,请至学习地图系统学习本博客的教程。加Telegram群可获得更多帮助喔! | 博客订阅:RSS | 广告招租请留言 | 博客VPS | 致谢渺软公益CDN |

评论

  1. tonie
    Windows Firefox 115.0
    9 月前
    2023-7-17 18:03:49

    学会了,不涉及这些依赖关系的话,有没有直接把docker run命令直接转换成docker compose文件的应用。。。

  2. 不是我黑
    Windows Edge 112.0.1722.58
    1 年前
    2023-4-24 20:21:53

    如果两个compose写的内容都用db这个会怎么样,会安装两个容器吗

    • 博主
      不是我黑
      Windows Edge 112.0.1722.58
      1 年前
      2023-4-24 20:26:53

      是又不是。 “是”——如果两个在不同目录的docker-compose都安装了一个叫db的容器,默认情况下该db的容器名是文件夹名-db-1而不是db。由于目录的名字一般是不重复的,所以两个容器是不冲突的。总之,docker-compose一定会保证不同容器有不同的容器名。
      “不是”——虽然有两个容器,但是只需要一个镜像(image)。 因此,除了卷(volume)的体积外,镜像是不会重复下载的(除非你使用了两个不同版本的镜像)。
      具体可以见这个文章的介绍: Docker系列 了解Docker Compose的配置文件
      不过,我觉得理解这件事最快的方法——就是你自己试试看。 加油喔 (ฅ´ω`ฅ)

      • 不是我黑
        Bensz
        Windows Edge 112.0.1722.58
        1 年前
        2023-4-24 20:31:04

        那如果两个compose想要复用同一个数据库,第二个服务有办法用db-1吗,主要不懂compose保存的时候是否要带上依赖服务,如果有很多容器想备份,怎么样才能性能和速度平衡,如果假定很compose都有db,那创建出很多db服务,那不是就很浪费服务器性能了

      • 博主
        不是我黑
        Windows Edge 112.0.1722.58
        1 年前
        2023-4-24 20:37:50

        你说的对,像你说的那种方式也是行得通的。 你可以设置一个db,然后其它的前端应用与它交互。只要它们和这个db处于同一个局域网段就行。不过,我比较少这样做。 我比较喜欢一个stack一个db——虽然占用资源,但是方便备份和迁移。 比如,如果我要将chatgpt-web迁移到其它服务器里,我直接将整个根目录复制过去就行,完全不会影响其它应用的使用。 这其实是个人喜好问题,没有必然要怎么做。 此外,本教程中使用的mongodb的volume还蛮大的,最省资源的方案确实是共用一个db。不过我就装了2个chatgpt-web(一个API一个Access Token),还hold得住 Σ( ° △ °|||)︴

      • 不是我黑
        Bensz
        Windows Edge 112.0.1722.58
        1 年前
        2023-4-24 20:42:27

        明白了!感谢博主,就是最麻烦的还是数据迁移和备份,所以牺牲点性能是为了更方便!如果有特别需求,再另外特别处理。

      • 博主
        不是我黑
        Windows Edge 112.0.1722.58
        1 年前
        2023-4-24 20:43:29

        差不多。我是用资源换方便和稳定。 至少个人用户可以这样折腾。 至于企业环境是怎么样,我也不太清楚。没有这方面的从业经验 (~ ̄▽ ̄)~

  3. Macintosh Chrome 112.0.0.0
    1 年前
    2023-4-14 10:33:32

    学习了!!!

    • 博主
      weijiajin
      Windows Edge 112.0.1722.39
      1 年前
      2023-4-14 10:47:38

      略懂一些 多多交流哈! 推荐加tg群: https://t.me/benszhub

  4. 月关
    Windows Edge 111.0.1661.44
    1 年前
    2023-3-29 19:19:29

    一字不落的看完了,受益匪浅,感谢苯苯

    • 博主
      月关
      Windows Edge 111.0.1661.54
      1 年前
      2023-3-29 19:25:02

      其实我对docker-compose的了解也是比较粗浅了,哈哈!不过,日常使用足够了。加油喔

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇