Docker 实用教程
1. 引言
在现代软件开发中,经常会遇到这样的场景:一个正在运行的 Web 应用用到了 Vue、Java 8、Java 15、Tomcat、Nginx、PHP、MySQL 和 Redis。如果要将这个应用迁移到一台新的服务器上运行,就需要在新机器上重新安装所有软件并配置环境变量,这是非常痛苦的过程。
Docker 的出现完美解决了这个问题。通过容器化技术,我们可以将应用及其运行环境打包成镜像,实现”一次构建,到处运行”的目标。而 docker-compose 则进一步简化了多容器应用的部署和管理。
2. 环境安装
2.1 安装 Docker
在 Linux 系统上,可以使用官方提供的安装脚本快速安装 Docker:
# 下载并执行 Docker 安装脚本
curl -fsSL https://get.docker.com | sh
# 启动 Docker 服务
sudo systemctl start docker
# 设置 Docker 开机自启
sudo systemctl enable docker
# 将当前用户添加到 docker 组(避免每次都需要 sudo)
sudo usermod -aG docker $USER
# 验证安装
docker --version
2.2 安装 docker-compose
docker-compose 是 Docker 官方的编排工具,用于定义和运行多容器应用:
# 下载 docker-compose
curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
# 赋予执行权限
chmod +x /usr/local/bin/docker-compose
# 验证安装
docker-compose --version
对于 Windows 和 macOS 用户,推荐安装 Docker Desktop,它已经包含了 Docker 和 docker-compose。
3. 核心概念
在深入使用 Docker 之前,需要理解几个核心概念。
3.1 镜像(Image)
Docker 镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的配置参数(如环境变量、用户等)。
镜像具有以下特点:
- 镜像是只读的,构建后内容不会改变
- 采用分层存储架构,便于复用和定制
- 可以理解为应用的”备份”或”快照”
Docker 利用 Union FS 技术,将镜像设计为分层存储架构。每一层构建完就不会再发生改变,后一层的改变只发生在自己这一层。
3.2 容器(Container)
容器是镜像的运行实例。镜像是静态的定义,容器是镜像运行时的实体。
容器的特点包括:
- 容器的实质是进程,但运行在独立的命名空间中
- 拥有自己的 root 文件系统、网络配置、进程空间等
- 容器存储层的生命周期与容器一样,容器销毁时数据也会丢失
镜像与容器的关系类似于面向对象编程中的类与实例,镜像是类,容器是实例。
3.3 仓库(Repository)
仓库是存储和分发镜像的服务。一个 Docker Registry 可以包含多个仓库,每个仓库可以包含多个标签(Tag)。
命名规范:
- 完整格式:
<仓库名>:<标签>
- 示例:
ubuntu:20.04
、nginx:latest
- 省略标签时默认使用
latest
4. 创建镜像
Docker 镜像的构建是通过读取 Dockerfile 文件来完成的,它本质是一个文本文件,其内包含了一条条的指令,每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。
4.1 编写配置文件
以开源项目的后端服务为例,在项目根目录创建 Dockerfile
:
# 指定基础镜像
FROM tomcat:9.0.41-jdk8-openjdk
# 复制 WAR 包到 Tomcat 的 webapps 目录
COPY ./chat-system-server.war /usr/local/tomcat/webapps/
# 复制配置文件
COPY ./tomcat/conf/server.xml /usr/local/tomcat/conf/server.xml
# 声明运行时端口
EXPOSE 8080
指令说明:
FROM
指定基础镜像COPY
复制文件到镜像内EXPOSE
声明服务运行时的端口号
4.2 常用指令
指令 | 用途 | 示例 |
---|---|---|
ADD |
从 URL 获取文件并放到目标路径 | ADD app.tar.gz /app/ |
RUN |
执行命令行命令 | RUN apt-get update |
CMD |
容器启动时运行的程序 | CMD ["java", "-jar", "app.jar"] |
ENV |
设置环境变量 | ENV PATH=/usr/bin:$PATH |
WORKDIR |
指定工作目录 | WORKDIR /app |
注意: 如果需要执行多个类似于 RUN
的指令时,请用 &&
来拼接,避免创建多层镜像:
RUN apt-get update && \
apt-get install -y gcc && \
apt-get clean
4.3 构建镜像
打开终端,进入 Dockerfile 文件所在目录,执行构建命令:
docker build -t chat-system-server:1.0.0 -f Dockerfile .
参数说明:
-t
指定容器名-f
指定配置文件(可选,默认使用 Dockerfile).
表示当前目录,指定构建镜像的上下文路径
5. 启动容器
启动容器有两种方式:一种是基于镜像新建一个容器并启动,另一种是启动一个处于终止状态的容器。
5.1 新建并启动
使用 docker run 镜像名
即可创建一个容器并启动它:
# 基本启动
docker run chat-system-server:1.0.0
容器启动后,你会发现通过镜像中声明的 8080 端口访问不了。这是因为容器启动后没有做端口映射,需要在启动命令中添加 -p
参数:
# 端口映射
docker run -p 127.0.0.1:8080:8080 chat-system-server:1.0.0
5.2 常用参数
后台运行:
docker run -d -p 127.0.0.1:8080:8080 chat-system-server:1.0.0
为容器命名:
docker run --name local_chat_system_server -d -p 127.0.0.1:8080:8080 chat-system-server:1.0.0
启动已终止容器:
docker container start 容器名
5.3 容器管理
# 终止容器
docker container stop 容器名
# 删除容器
docker container rm 容器名
# 进入容器
docker exec -it 容器名 bash
5.4 数据挂载
容器内存储的数据会随着容器的终止而丢失,需要挂载数据卷来实现数据的持久化存储。通常有两种做法:
数据卷:
# 创建数据卷
docker volume create chat-system-data
# 启动容器并挂载数据卷
docker run -d --name local_chat_system_server \
--mount source=chat-system-data,target=/usr/local/data \
chat-system-server:1.0.0
目录映射(推荐):
docker run -d --name local_chat_system_server \
-v /host/path:/container/path \
chat-system-server:1.0.0
目录映射的形式会把指定的主机路径与容器内的目标路径做关联,本地主机做的操作会响应到容器内,反之亦然。
6. 编排容器
现在回到文章开头所说的那个场景。如果全部打包到一个镜像里,后期维护与扩展将成为恶梦。一般这种场景我们都会使用 Docker Compose 来实现。
简而言之,Docker Compose 的作用就是将多个独立的容器组合起来,让容器之间可以轻易地互相访问,最终实现我们的需求。
6.1 编写配置文件
容器的编排是通过编写 docker-compose.yml
配置文件来实现的,一般我们会将这个文件创建在项目的根目录。配置文件包含以下主要部分:
version
- 指定 Docker Compose 文件的格式版本networks
- 用于自定义网络services
- 定义各种服务(MySQL、Redis、Nginx 等)
6.2 定义网络
在物理机上部署服务时,多个服务之间相互访问需要处于同一个网关下。在 docker-compose 中流程也是一样的,因此我们需要先定义一个网络:
networks:
app-network:
external: true
name: app-network
driver: bridge
ipam:
driver: default
config:
- subnet: 192.168.30.0/24
gateway: 192.168.30.1
网络配置说明:
192.168.30.0/24
表示从 192.168.30.1 到 192.168.30.254 的 IP 地址范围gateway
指定网关地址为 192.168.30.1driver: bridge
指定网络连接模式为桥接
6.3 定义服务
我们可以在 services
指令下定义需要的服务,为它们连接网络、挂载数据卷、设置时区、定义访问端口等。以 MySQL 为例:
services:
mysql:
image: mysql:5.7.42
container_name: local_mysql
volumes:
- /host/mysql_data:/var/lib/mysql
- /host/mysql_conf/my.cnf:/etc/my.cnf
ports:
- 3306:3306
networks:
app-network:
ipv4_address: 192.168.30.11
environment:
- MYSQL_ROOT_PASSWORD=xxxx
- TZ=Asia/Shanghai
配置说明:
mysql
- 服务名称image
- 镜像名称container_name
- 容器名称volumes
- 挂载的数据卷ports
- 端口映射networks
- 服务接入的网络和分配的 IP 地址environment
- 环境变量设置
通过这几行配置,我们就拥有了一个 MySQL 服务,其他服务可以通过 192.168.30.11:3306
访问到这个服务。
6.4 完整示例
以下是一个包含多个服务的完整配置示例:
version: '3.8'
networks:
app-network:
driver: bridge
ipam:
config:
- subnet: 192.168.30.0/24
gateway: 192.168.30.1
services:
mysql:
image: mysql:5.7.42
container_name: local_mysql
volumes:
- ./mysql_data:/var/lib/mysql
ports:
- 3306:3306
networks:
app-network:
ipv4_address: 192.168.30.11
environment:
- MYSQL_ROOT_PASSWORD=rootpassword
- TZ=Asia/Shanghai
redis:
image: redis:6-alpine
container_name: local_redis
ports:
- 6379:6379
networks:
app-network:
ipv4_address: 192.168.30.12
environment:
- TZ=Asia/Shanghai
app-backend:
image: tomcat:9.0.41-jdk8-openjdk
container_name: chat_system_server
ports:
- 8080:8080
volumes:
- ./webapps:/usr/local/tomcat/webapps
- ./data:/usr/local/data
environment:
- TZ=Asia/Shanghai
networks:
app-network:
ipv4_address: 192.168.30.13
depends_on:
- mysql
- redis
nginx:
image: nginx:1.18.0
container_name: local_nginx
ports:
- 80:80
- 443:443
volumes:
- ./nginx_config:/etc/nginx
environment:
- TZ=Asia/Shanghai
networks:
- app-network
depends_on:
- app-backend
注意: 上面配置中使用了 depends_on
指令定义服务的启动顺序,确保数据库先启动。
6.5 启动服务
在终端通过以下命令启动所有定义的服务:
# 启动所有服务
docker-compose up
# 后台启动
docker-compose up -d
# 停止服务
docker-compose down
6.6 环境变量管理
实际使用时,本地路径一般会通过变量的形式注入:
# 在 .env 文件中定义
MY_VOLUME_PATH=/path/to/your/volume
# 在 docker-compose.yml 中使用
volumes:
- ${MY_VOLUME_PATH:-/default/path}/webapps:/usr/local/tomcat/webapps
启动时传入变量:
MY_VOLUME_PATH=/path/to/your/volume docker-compose up