Bootstrap

深入理解 Dockerfile 和 docker-compose[实战篇]

🏝️ 博主介绍

大家好,我是 一个搬砖的农民工,很高兴认识大家 😊 ~
👨‍🎓 个人介绍:本人是一名后端Java开发工程师,坐标北京 ~
🎉 感谢关注 📖 一起学习 📝 一起讨论 🌈 一起进步 ~
🙏 作者水平有限,欢迎各位大佬指正留言,相互学习进步 ~

🌱 Dockerfile和docker-compose是Docker中两个非常重要的工具。Dockerfile提供了创建Docker镜像的手段,而docker-compose则提供了一种管理和运行多个服务的有效方法。对于开发者来说理解并掌握这两个工具在实际工作中会有很大帮助。🍂

1. Dockerfile 🚀

Dockerfile是一个由一系列命令和参数构成的脚本,这些命令应用于基础镜像,用于创建一个新的Docker 镜像

🌈 1.1 Dockerfile 常用参数

  • FROM:指定基础镜像。
    示例:FROM openjdk:17-jdk-slim。
  • MAINTAINER(或 LABEL maintainer):设置镜像创建者的信息。
    示例:LABEL maintainer=“John Doe”。
  • LABEL:除了之前提到的 LABEL maintainer,LABEL 还可以用于为镜像添加其他元数据。
    示例:LABEL version=“1.0” description=“My Java application”。
  • EXPOSE:声明容器运行的服务端口,但并不会实际发布端口。要发布端口,需要在 docker run 时使用 -p 或 -P 参数。
    示例:EXPOSE 8080。
  • ENV:设置环境变量
    示例:ENV JAVA_HOME=/usr/local/openjdk-17。
  • ADD:将本地文件、目录或远程 URL 添加到容器中,并自动解压 tar 文件,可以访问网络资源。
    示例:ADD myapp.jar /app/。
  • COPY:将本地文件或目录复制到容器中,不会解压文件,也不能访问网络资源。
    示例:COPY myapp.jar /app/。
  • RUN:在镜像构建时执行的命令。
    示例:RUN apt-get update && apt-get install -y curl。
  • CMD:指定容器启动时默认运行的命令,可以被docker run之后的参数替换。
    示例:CMD [“java”, “-jar”, “myapp.jar”]。
  • ENTRYPOINT:指定容器启动时运行的命令,不会被docker run之后的参数替换。
    示例:ENTRYPOINT [“java”, “-jar”]。
  • VOLUME:用于在容器中创建挂载点,可以链接到本地机文件系统,或者其他容器。
    示例:VOLUME [“/data”]。
  • USER:设置运行容器时的用户名或UID
    示例:USER appuser。
  • WORKDIR:设置工作目录,对后续的 RUN、CMD、ENTRYPOINT、ADD、COPY 等指令生效。
    示例:WORKDIR /app。
  • ONBUILD:为镜像创建者添加触发器,当该镜像被用作另一个 Dockerfile 的基础镜像(即被 FROM 指令引用)时,这些触发器将在后续的构建步骤中执行。
    示例:ONBUILD COPY . /app/src。

🌈 1.2 Dockerfile 模板

# AdoptOpenJDK 停止发布 OpenJDK 二进制,而 Eclipse Temurin 是它的延伸,提供更好的稳定性
FROM eclipse-temurin:8-jre

## 创建目录,并使用它作为工作目录
RUN mkdir -p /opt/pro_xx/jar/springboot-basis
WORKDIR /opt/pro_xx/jar/springboot-basis
## 将后端项目的 Jar 文件,复制到镜像中
COPY ./springboot-basis/springboot-basis.jar springboot-basis.jar

## 设置 TZ 时区
## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖
ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms128m -Xmx128m"

## 暴露后端项目的 8082 端口
EXPOSE 8082

## 启动后端项目
## -Djava.security.egd=file:/dev/./urandom 是一个 Java 系统属性设置,用于指定随机数生成器的源
## /dev/urandom:这是一个非阻塞型随机数生成器
CMD java ${JAVA_OPTS} -Xms128m -Xmx128m -Djava.security.egd=file:/dev/./urandom -jar springboot-basis.jar

2. docker-compose 🚀

🌈 2.1 docker-compose 配置文件

docker-compose是一个用于定义和运行多容器Docker应用程序的工具。你可以使用YAML文件来定义应用程序的服务,然后一条命令就可以创建和启动所有的服务。

以下是一些主要的docker-compose参数:

# Docker Compose 文件版本,定义了你使用的 Compose 文件格式  
version: '3.8'  # 版本号,这里使用3.8,可以根据需要选择不同版本  
  
# 定义服务,服务可以是一个或多个容器  
services:  
  # 定义一个名为 nginx 的服务,基于 Nginx 镜像  
  nginx:  
    # 使用的镜像,可以是 Docker Hub 上的镜像或者本地构建的镜像  
    image: nginx:latest  # 使用最新的 Nginx 镜像  
    # 容器启动时运行的命令,通常用于覆盖镜像默认的启动命令  
    command: [nginx-debug, '-g', 'daemon off;']  # 以调试模式运行 Nginx,并且前台运行以保持容器活动  
    # 容器内的工作目录  
    working_dir: /usr/share/nginx/html  # Nginx 默认的网页目录  
    # 端口映射,格式为 "主机端口:容器端口"  
    ports:  
      - "80:80"  # 将主机的 80 端口映射到容器的 80 端口  
    # 环境变量,用于在容器内设置环境变量  
    environment:  
      - NGINX_ROOT=/usr/share/nginx/html  # 设置 Nginx 的根目录环境变量  
    # 挂载的卷,格式为 "主机路径:容器路径"  
    volumes:  
      - web-root:/usr/share/nginx/html  # 将主机上的卷 web-root 挂载到容器的 /usr/share/nginx/html 目录  
    # 网络设置,定义容器加入的网络  
    networks:  
      - webnet  # 将容器加入到 webnet 网络  
    # 部署配置,仅在使用 Docker Swarm 集群时有用  
    deploy:  
      replicas: 2  # 定义服务的副本数量为2  
      update_config:  
        parallelism: 2  # 同时更新的容器数量  
        delay: 10s  # 每个容器更新的延迟时间  
      restart_policy:  
        condition: on-failure  # 重启策略,仅在容器失败时重启  
  
  # 定义一个名为 backend 的服务,基于一个简单的 Python 应用镜像  
  backend:  
    # 构建的上下文路径和 Dockerfile 路径,用于从 Dockerfile 构建镜像  
    build:  
      context: ./  # 构建上下文是当前目录  
      dockerfile: Dockerfile.backend  # 使用的 Dockerfile 文件名为 Dockerfile.backend  
    # 容器启动时运行的命令,覆盖镜像默认的启动命令  
    command: ["python", "app.py"]  # 启动 Python 应用  
    # 端口映射  
    ports:  
      - "5000:5000"  # 将主机的 5000 端口映射到容器的 5000 端口  
    # 依赖的服务,定义服务启动顺序,先启动 web 服务  
    depends_on:  
    # 在启动 backend 服务之前先启动 web 服务 
      - nginx # 注意:当前服务不会再依赖服务【完全启动】后启动,会在nginx服务启动一部分后就启动backend服务  
    # 环境变量  
    environment:  
      - FLASK_ENV=development  # 设置 Flask 的环境变量为开发模式  
    # 网络设置  
    networks:  
      - webnet  # 将容器加入到 webnet 网络  
    # 部署配置  
    deploy:  
      resources:  
        limits:  
          cpus: '0.50'  # 限制容器使用的 CPU 资源为 0.5 个  
          memory: 512M  # 限制容器使用的内存资源为 512MB  
      restart_policy:  
        condition: always  # 重启策略,无论何种情况下都重启容器  
    restart: on-failure  # 添加重启策略,仅在容器失败时重启  
    container_name: my_backend_container  # 指定容器名称为 my_backend_container  
    env_file:  
      - backend.env  # 从 backend.env 文件中加载环境变量  
  
# 定义卷,用于持久化数据  
volumes:  
  web-root:  # 定义一个名为 web-root 的卷,用于存储 Nginx 的静态文件  
  
# 定义网络,用于服务间的通信  
networks:  
  webnet:  # 定义一个名为 webnet 的网络,服务间的通信将在这个网络中进行  
    driver: bridge  # 使用 Docker 默认的 bridge 驱动
  • version:version设定docker-compose的版本,这一版本需要与Docker Engine的版本匹配
  • services:services定义了要创建和启动的服务集。每个服务使用一个容器。
  • build:构建镜像的上下文路径,你也可以通过dockerfile参数指定Dockerfile的名称和位置,或指定image直接拉取镜像运行
  • ports:配置端口映射,即把容器的端口映射到主机上。
  • volumes:定义挂载卷。可以是匿名卷、主机卷或者命名卷。
  • environment:设置环境变量
  • depends_on:定义了服务之间的依赖关系。Docker会确保依赖的服务先启动。
  • links:链接到其他服务。
  • command:重写容器启动的命令。
  • restart:重启策略。
    no:这是默认值,表示容器不会在退出时自动重启。
    always:无论容器的退出状态码是什么,都会自动重启容器。
    on-failure:只有当容器的退出状态码非零时,才会自动重启容器。
    unless-stopped:类似于 always,但在 Docker 守护进程停止或重启时,如果容器也被停止,则不会重启容器。
  • env_file:外部文件中加载环境变量,作用和 environment 一样。这对于管理敏感信息(如数据库密码)特别有用,因为你可以将这些信息存储在文件中,而不是直接写在 docker-compose.yml 文件中。文件应该包含一行一个的环境变量,格式如 VAR_NAME=value。
  • container_name:为容器指定一个自定义的名称,而不是使用 Docker 自动生成的名称。这有助于在需要时更容易地识别和管理容器。

🌈 2.2 docker-compose 常用命令

注:docker-compose 名称必须在对应项目下面执行。

  • docker-compose images:查看 Docker Compose 配置文件中定义的服务所使用的镜像。

例:docker-compose images 仅显示 Docker Compose 配置文件中定义的服务所使用的镜像。而 docker images :列出本地主机上所有已下载的 Docker 镜像,包括 docker-compose 配置中已经下载的镜像

  • docker-compose up:使用 docker-compose.yml 文件启动并运行你的应用。如果服务已经存在,则默认会先停止并重新创建服务。
    -f:指定要使用的 docker-compose.yml 文件路径;
    --build:重新构建镜像;
    -d:后台运行

例:docker-compose up -d 这个命令将以分离模式(后台运行)启动并运行你的服务。
docker-compose up nginx 仅启动nginx 服务。

  • docker-compose down [服务ID]:停止并删除容器,网络,网络。

例:docker-compose down --volumes 这个命令会停止你当前目录所有服务,并删除容器,网络和卷。

  • docker-compose pull:拉取服务依赖的镜像。

例:docker-compose pull 这个命令会拉取 docker-compose.yml 文件中定义的服务所依赖的所有镜像。

  • docker-compose build:构建或重新构建服务。

例:docker-compose build 这个命令会构建你的服务。你也可以使用 docker-compose build --no-cache 来重新构建映像。

  • docker-compose run:在单个服务上运行一次性命令。

例:docker-compose run web bash 这个命令会启动 web 服务容器,并连接到 bash shell。

  • docker-compose exec:在运行的容器上执行命令。

例:docker-compose exec web bash 这个命令会在运行的 web 服务容器上连接到 bash shell。

  • docker-compose logs:查看容器的输出。- - tail 选项可以指定查看日志的最后几行

例:docker-compose logs -f web 这个命令会查看 web 服务的输出,-f 参数表示跟随(即实时显示日志输出)。

  • docker-compose ps:列出项目中的所有容器。

例:docker-compose ps -a 这个命令会列出你的项目中正在运行的所有容器。

  • docker-compose config:检查配置

例:docker-compose config 检查配置;docker-compose config -q 检查配置,有问题才输出

  • docker-compose start [服务ID]:启动 docker compose 服务

例:docker-compose start 启动容器服务

  • docker-compose restart:重启 docker compose 服务

例:docker-compose restart 重启容器服务

  • docker-compose stop :停止 docker compose 服务

例:docker-compose stop 停止容器服务

  • docker-compose rm [服务ID] :删除已经停止的服务容器。

🌈 2.3 docker-compose.yaml 配置文件模板

前提:需要安装 docker 和 docker compose

例:在 /opt 目录下新建 docker-compose.yaml 、 docker-compose.yml 、compose.yml、compose.yaml 任意一种名称文件,添加如下内容。(如果不是上面四种格式命名则需要如 mysql.yml,则需要用 docker-compose -f mysql.yml up -d 形式执行命令)

# version: '3.8'  新版本不需要指定 
services:  
  # mysql服务
  mysql:  
    image: mysql:5.7  
    container_name: mysql_container  
    environment:  
      MYSQL_ROOT_PASSWORD: root # 用户root的密码
    volumes:  
      - ./mysql:/var/lib/mysql  
    ports:  
      - "3306:3306"  
    networks:  
      - app_network  
    restart: unless-stopped  # 重启策略,类似于 always
  # redis服务
  redis:  
    image: redis:latest 
    container_name: redis_container  
    ports:  
      - "6379:6379"  
    volumes:  
      - ./redis:/data  
    networks:  
      - app_network  
    restart: unless-stopped
  # nginx服务
  nginx:  
    image: nginx:latest 
    container_name: nginx_container  
    ports:  
      - "80:80"  
      - "443:443"  
    volumes:  
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf # 需要先导入nginx.conf文件,不然会报错。因为系统创建的nginx.conf是个目录
      - ./nginx/html:/usr/share/nginx/html
      - ./nginx/log:/var/log/nginx
    networks:  
      - app_network  
    restart: unless-stopped  
  # MongoDB 服务  
  mongo:  
    image: mongo:latest
    container_name: mongo_container
    # 用户权限目前还没弄明白,先屏蔽
    # environment:  
    #   MONGO_INITDB_ROOT_USERNAME: root  
    #   MONGO_INITDB_ROOT_PASSWORD: 123456  
    #   MONGO_INITDB_ROOT_ROLE: root  # 赋予 root 用户最高权限  
    volumes:  
      - ./mongo:/data/db  # 将 MongoDB 的数据目录映射到宿主机的 ./mongo 目录  
    ports:  
      - "27017:27017"  # 将 MongoDB 的默认端口映射到宿主机的 27017 端口  
    networks:  
      - app_network  
    restart: unless-stopped  # 重启策略  
# 网络
networks:  
  app_network:  
    driver: bridge

执行 docker-compose up -d 即可下载镜像别运行服务实例

🌈 2.4 docker-compose.yaml 编排示例

🥝 2.4.1 环境配置

  • docker 版本: 26.1.4
  • Docker Compose 版本:v2.27.3

🥝 2.4.2 docker-compose.yaml

# version: '3.8'  新版本不需要指定 
services:  
  # mysql服务
  mysql:  
    image: mysql:5.7  
    container_name: mysql_container  
    environment:  
      MYSQL_ROOT_PASSWORD: root # 用户root的密码
    volumes:  
      - ./mysql:/var/lib/mysql  
    ports:  
      - "3306:3306"  
    networks:  
      - app_network  
    restart: unless-stopped  # 重启策略,类似于 always
  # redis服务
  redis:  
    image: redis:latest  
    container_name: redis_container  
    ports:  
      - "6379:6379"  
    volumes:  
      - ./redis:/data  
    networks:  
      - app_network  
    restart: unless-stopped
  # nginx服务
  nginx:  
    image: nginx:latest  
    container_name: nginx_container  
    ports:  
      - "80:80"  
      - "443:443"  
    volumes:  
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf # 需要先导入nginx.conf文件,不然会报错。因为系统创建的nginx.conf是个目录
      - ./nginx/html:/usr/share/nginx/html
      - ./nginx/log:/var/log/nginx
    networks:  
      - app_network  
    restart: unless-stopped  
  # MongoDB 服务  
  mongo:  
    image: mongo:latest   
    container_name: mongo_container  
    environment:  
      MONGO_INITDB_ROOT_USERNAME: root  # 可选,设置 root 用户的用户名  
      MONGO_INITDB_ROOT_PASSWORD: root  # 可选,设置 root 用户的密码  
    volumes:  
      - ./mongo:/data/db  # 将 MongoDB 的数据目录映射到宿主机的 ./mongo 目录  
    ports:  
      - "27017:27017"  # 将 MongoDB 的默认端口映射到宿主机的 27017 端口  
    networks:  
      - app_network  
    restart: unless-stopped  # 重启策略  
# 网络
networks:  
  app_network:  
    driver: bridge

🥝 2.4.3 创建项目

[root@localhost opt]# mkdir pro_xx
[root@localhost opt]# cd pro_xx
[root@localhost pro_xx]# mkdir nginx

在pro_xx目录下导入 docker-compose.yaml 文件
在pro_xx/nginx下导入 nginx.conf 文件

# nginx.conf模板
worker_processes  1;
events {
    worker_connections  1024;
}
http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;
    server {
        listen       80;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}

🥝 2.4.4 启动 docker-compose.yaml

回到 pro_xx 目录,执行 docker-compose up -d 启动服务,docker-compose ps 查看服务是否启动成功

[root@localhost pro_xx]# docker-compose up -d
[+] Running 4/4
 ✔ Network pro_xx_app_network  Created                                                                                        0.1s
 ✔ Container mysql_container   Started                                                                                        0.5s
 ✔ Container redis_container   Started                                                                                        0.5s
 ✔ Container nginx_container   Started                                                                                        0.5s

[root@localhost pro_xx]# docker-compose ps
NAME              IMAGE          COMMAND                  SERVICE   CREATED          STATUS          PORTS
mysql_container   mysql:5.7      "docker-entrypoint.s…"   db        21 seconds ago   Up 19 seconds   0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060/tcp
nginx_container   nginx:latest   "/docker-entrypoint.…"   nginx     21 seconds ago   Up 19 seconds   0.0.0.0:80->80/tcp, :::80->80/tcp, 0.0.0.0:443->443/tcp, :::443->443/tcp
redis_container   redis:latest   "docker-entrypoint.s…"   redis     21 seconds ago   Up 19 seconds   0.0.0.0:6379->6379/tcp, :::6379->6379/tcp

docker-compose logs 可以查看服务运行日志:

[root@localhost pro_xx]# docker-compose logs
...
mysql_container  | 2024-10-12T19:59:52.758021Z 0 [Note] Server hostname (bind-address): '*'; port: 3306
mysql_container  | 2024-10-12T19:59:52.758070Z 0 [Note] IPv6 is available.
mysql_container  | 2024-10-12T19:59:52.758088Z 0 [Note]   - '::' resolves to '::';
nginx_container  | /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
nginx_container  | /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
nginx_container  | /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
nginx_container  | 10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
nginx_container  | 10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
nginx_container  | /docker-entrypoint.sh: Sourcing /docker-entrypoint.d/15-local-resolvers.envsh
nginx_container  | /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
nginx_container  | /docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
nginx_container  | /docker-entrypoint.sh: Configuration complete; ready for start up
mysql_container  | 2024-10-12T19:59:52.758109Z 0 [Note] Server socket created on IP: '::'.
mysql_container  | 2024-10-12T19:59:52.758780Z 0 [Warning] Insecure configuration for --pid-file: Location '/var/run/mysqld' in the path is accessible to all OS users. Consider choosing a different directory.
mysql_container  | 2024-10-12T19:59:52.763935Z 0 [Note] Event Scheduler: Loaded 0 events
mysql_container  | 2024-10-12T19:59:52.764089Z 0 [Note] mysqld: ready for connections.
mysql_container  | Version: '5.7.44'  socket: '/var/run/mysqld/mysqld.sock'  port: 3306  MySQL Community Server (GPL)

验证mysql是否启动成功:

[root@localhost pro_xx]# docker-compose exec db bash

bash-4.2# mysql -u root -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.7.44 MySQL Community Server (GPL)

Copyright (c) 2000, 2023, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
4 rows in set (0.00 sec)

🥝 2.4.5 开通防火墙端口

⚡ 推荐参考:CentOS 系统如何在防火墙开启端口

sudo firewall-cmd --zone=public --permanent --add-port=3306/tcp
sudo firewall-cmd --zone=public --permanent --add-port=6379/tcp
sudo firewall-cmd --zone=public --permanent --add-port=80/tcp
sudo firewall-cmd --zone=public --permanent --add-port=443/tcp
sudo firewall-cmd --reload
sudo firewall-cmd --zone=public --list-ports

在这里插入图片描述

🥝 2.4.6 连接到服务

电脑通过cmd打开命令提示符,运行下面指令能正常访问mysql和redis则说明防火墙开通成功。192.168.159.128为虚拟机ip

C:\Users>redis-cli -h 192.168.159.128 -p 6379

192.168.159.128:6379> keys *
(empty list or set)
192.168.159.128:6379> ^C

C:\Users>mysql -h 192.168.159.128 -u root -proot
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 3
Server version: 5.7.44 MySQL Community Server (GPL)

Copyright (c) 2000, 2021, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>

3 构建java镜像 🚀

⚡ 推荐参考:Dockerfile + docker-compose 构建java镜像并运行服务

;