Docker基本概念、安装配置、容器化部署及常用命令。
前言
Docker是一种操作系统层面的轻量级虚拟化技术,由于隔离的进程独立于宿主和其它的隔离的进程,因此也称其为容器(Container)。与虚拟机(VM)相比,Docker使用宿主机的操作系统,启动更快。每个容器只运行所需的应用程序和依赖项,资源消耗更少。Docker将操作系统、运行时环境、第三方软件库和依赖包、应用程序、环境变量、配置文件、启动命令等打包在一起,以便在任何环境中都能正常运行。
基本概念
Docker Daemon
Docker使用Client-Server架构,Docker Clinet和Docker Daemon之间通过Socket或Restful API进行通信。
Docker Daemon是服务端的守护进程,负责管理Docker的各种资源,接受并处理来自客户端的请求,然后将结果返回给客户端。
Docker镜像与容器
镜像(Images)是一个只读的容器模板,含有启动Docker容器所需的文件系统结构及内容。容器是Docker的运行实例,它提供了一个独立的可移植的环境。Docker以镜像和在镜像基础上构建的容器为基础,以容器开发、测试、发布的单元将应用相关的所有组件和环境进行封装,避免了应用在不同平台间迁移所带来的依赖问题,确保了应用在生产环境的各阶段达到高度一致的实际效果。
Docker仓库
Docker仓库(Registry)是用来集中存储和管理Docker镜像的地方。常用的有Dockerhub,用户可在此分享和下载Docker镜像,以实现镜像的共享和复用。
安装配置
Docker官网在国内需要vpn才能访问,可通过国内镜像地址下载。Docker的使用可通过命令行方式,也可通过图形化工具Docker Desktop。
Windows系统中启动Docker Desktop的先决条件(以下二选一)
容器化与Dockerfile
Dockerfile是Docker用来构建镜像的指令文件,Docker容器化包含以下三个部分
创建一个Dockerfile
使用Dockerfile构建镜像
使用镜像创建和构建容器
例如要使用node在alpine中运行一个index.js文件,对应的Dockerfile为
1 2 3
| FROM node:14-alpine COPY index.js /index.js CMD node /index.js
|
.dockerignore是Docker镜像构建的ignore文件,通过合理配置可以减少Docker构建上下文大小,加快镜像构建速度,避免将敏感信息(如开发环境配置)打包进镜像,保持生产镜像的简洁性。
Docker Compose
Docker Compose是一个用来定义和运行复杂应用的Docker工具。一个使用Docker容器的应用,通常由多个容器组成。使用Docker Compose不再需要使用shell脚本来启动容器。
Docker Compose通过一个配置文件来管理多个Docker容器。在配置文件中,所有的容器通过services来定义,然后使用docker-compose脚本来启动,停止和重启应用、应用中的服务以及所有依赖服务的容器,非常适合组合使用多个容器进行开发的场景。
最新的Docker已经集成了docker-compose功能,可使用docker compose version命令查看当前版本。
简单示例
下面是一个Docker Compose的简单示例。
配置文件目录结构
1 2 3 4 5 6 7 8 9 10 11 12
| ├─ etc └─ docker ├─ mysql │ ├─ config │ │ └─ my.conf │ ├─ sqlScript │ │ └─ createDatabase.sql │ └─ Dockerfile │ └─ redis └─ conf └─ redis.conf
|
配置文件docker-compose.yml
Docker Compose配置文件是一个定义服务,网络和卷的YAML文件,默认文件名为docker-compose.yml。与Docker运行一样,默认情况下,Dockerfile中指定的选项(例如,CMD,EXPOSE,VOLUME,ENV)都被遵守,你不需要在docker-compose.yml中再次指定它们。
例如定义一个包含redis和mysql数据的容器,可使用以下配置文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| version: '3.7'
services:
redis: container_name: redis ports: - "6379:6379" image: redis volumes: - "./redis/datadir:/data" - "./redis/conf/redis.conf:/usr/local/etc/redis/redis.conf" - "./redis/logs:/logs" command: /bin/bash -c "redis-server /usr/local/etc/redis/redis.conf" mysql-db: container_name: mysql-db ports: - "3306:3306" image: mysql:8.0.1 volumes: - "./mysql/data:/var/lib/mysql" - "./mysql/config:/etc/mysql/conf.d" build: context: . dockerfile: mysql/Dockerfile environment: MYSQL_ROOT_PASSWORD: "root"
|
可使用以下命令构建并运行容器(使用-f使用自定义的配置文件,使用-d以后台方式运行)
docker-compose -f server.yml up -d
关闭或移除容器、镜像等
docker-compose -f server.yml down
完整示例
将一个.NET+Vue+MySQL应用打包为Docker镜像的完整方案如下,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| my-app/ ├── mysql │ ├─ config │ │ └─ my.conf │ ├─ data │ ├─ sqlScript │ │ └─ init.sql │ └─ Dockerfile │ ├── backend/ │ ├── ABPDemo.Web │ │ └── ABPDemo.Web.csproj │ ├── Dockerfile │ ├── .dockerignore │ └── ... │ ├── frontend/ │ ├── package.json │ ├── Dockerfile │ ├── .dockerignore │ ├── nginx.conf │ └── ... ├── docker-compose.yml └── .env
|
配置数据库Dockerfile
数据库Dockerfile文件如下,
1 2
| FROM mysql:8.0.1 COPY mysql/sqlScript/*.sql /docker-entrypoint-initdb.d/
|
其中init.sql数据库初始化脚本文件如下,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0;
CREATE DATABASE IF NOT EXISTS `ABPDemo` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE `ABPDemo`;
CREATE TABLE IF NOT EXISTS `students` ( `id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '数据唯一标识', `name` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '姓名', PRIMARY KEY (`id`) USING BTREE, )ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;
INSERT INTO students (id, name) VALUES('3a1b6256-17fb-3551-0aed-af1436e871f1', 'John');
SET FOREIGN_KEY_CHECKS = 1;
|
my.conf数据库配置文件如下,
1 2 3 4 5 6
| [mysqld] character-set-server=utf8mb4 default-time-zone='+8:00' innodb_rollback_on_timeout='ON' max_connections=500 innodb_lock_wait_timeout=500
|
注:如果MySQL容器初始化脚本未执行,可使用命令cmd.exe /c "docker exec -i mysql_db mysql -uroot -p[MYSQL_ROOT_PASSWORD] < ./mysql/sqlScript/init.sql"手动执行。
配置后端Dockerfile
.NET后端Dockerfile文件如下,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base WORKDIR /app EXPOSE 80 EXPOSE 443
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build WORKDIR /src
COPY *.sln .
COPY ["ABPDemo.Web/ABPDemo.Web.csproj", "ABPDemo.Web/"] COPY ["ABPDemo.HttpApi/ABPDemo.HttpApi.csproj", "ABPDemo.HttpApi/"] COPY ["ABPDemo.Application.Contracts/ABPDemo.Application.Contracts.csproj", "ABPDemo.Application.Contracts/"] COPY ["ABPDemo.Domain.Shared/ABPDemo.Domain.Shared.csproj", "ABPDemo.Domain.Shared/"] COPY ["ABPDemo.Application/ABPDemo.Application.csproj", "ABPDemo.Application/"] COPY ["ABPDemo.Domain/ABPDemo.Domain.csproj", "ABPDemo.Domain/"] COPY ["ABPDemo.EntityFrameworkCore/ABPDemo.EntityFrameworkCore.csproj", "ABPDemo.EntityFrameworkCore/"] RUN dotnet restore "ABPDemo.Web/ABPDemo.Web.csproj"
COPY . . WORKDIR "/src/ABPDemo.Web" RUN dotnet build "ABPDemo.Web.csproj" -c Release -o /app/build
FROM build AS publish RUN dotnet publish "ABPDemo.Web.csproj" -c Release -o /app/publish
FROM base AS final WORKDIR /app COPY --from=publish /app/publish . ENTRYPOINT ["dotnet", "ABPDemo.Web.dll"]
|
配置前端Dockerfile
Vue前端Dockerfile文件如下,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| FROM node:18-alpine AS build WORKDIR /app COPY package*.json ./ RUN npm install COPY . .
ARG VUE_APP_API_BASE_URL=http://localhost:8081/ ENV VUE_APP_BASE_URL=$VUE_APP_API_BASE_URL RUN npm run build:prod
FROM nginx:1.27.0 COPY --from=build /app/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]
|
其中nginx.conf的配置内容如下,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| events { worker_connections 1024; }
http { include /etc/nginx/mime.types; default_type application/octet-stream;
server { listen 80; server_name localhost; root /usr/share/nginx/html; index index.html;
# 处理前端路由 location / { try_files $uri $uri/ /index.html; }
# 代理 API 请求到后端 location /api/ { proxy_pass http://backend:80; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } }
|
注:Dockerfile中配置的后端地址http://localhost:8081/为代理地址(由于浏览器同源策略,必须与宿主机的前端访问地址一致),由于宿主机的8081端口指向容器的80端口,因此容器内实际的前端地址仍然为http://localhost:80/。 在location中通过设置proxy_pass相对路径http://backend:80(Docker内置的DNS解析器会自动将backend解析为对应容器的IP地址),将后端请求由http://localhost:80/api/xxx转发到http://backend:80/api/xxx,从而解决跨域问题。
配置Docker Compose
docker-compose.yml配置文件如下,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| version: '3.8'
services: mysql: image: mysql:8.0.1 container_name: mysql_db environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} MYSQL_DATABASE: ${MYSQL_DATABASE} MYSQL_USER: ${MYSQL_USER} MYSQL_PASSWORD: ${MYSQL_PASSWORD} ports: - "3307:3306" volumes: - ./mysql/data:/var/lib/mysql - ./mysql/config:/etc/mysql/conf.d - ./mysql/sqlScript:/docker-entrypoint-initdb.d networks: - app-network command: - --default-authentication-plugin=mysql_native_password - --character-set-server=utf8mb4 - --collation-server=utf8mb4_unicode_ci - --ssl=0
backend: build: ./backend container_name: dotnet_backend environment: - ConnectionStrings__DefaultConnection=Server=mysql;Port=3306;Database=${MYSQL_DATABASE};User=${MYSQL_USER};Password=${MYSQL_PASSWORD} - ASPNETCORE_ENVIRONMENT=Production ports: - "5000:80" depends_on: - mysql networks: - app-network restart: unless-stopped
frontend: build: ./frontend container_name: vue_frontend ports: - "8081:80" depends_on: - backend networks: - app-network restart: unless-stopped
volumes: mysql_data:
networks: app-network: driver: bridge
|
配置环境变量
.env配置文件如下,
1 2 3 4
| MYSQL_ROOT_PASSWORD=root1234 MYSQL_DATABASE=ABPDemo MYSQL_USER=root MYSQL_PASSWORD=root1234
|
使用命令docker-compose build以构建上述所有镜像,使用命令docker-compose up -d启动镜像。
导入导出
管道方式打包导出所有镜像(使用此命令导出的镜像,在加载后为<none>:<none>未标签状态,需要手动打标签)
docker save -o all-images.tar $(docker-compose images -q)
列表方式打包导出所有镜像(列表项格式为<image-name>:<image-tag>)
docker save -o all-images.tar mysql:8.0.1 my-app-backend:latest my-app-frontend:latest
也可以将所有镜像罗列到image-list.txt当中,然后通过文件读取的方式导出
docker save -o all-images.tar $(cat image-list.txt)
注:使用列表导出时,请检查所有Dockfile中依赖的基础镜像,例如node:18-alpine等,将他们全部添加进列表中,否则后续docker-compose构建容器时会尝试联网拉取远程基础镜像。
导出配置
docker-compose config > docker-compose-export.yml
加载镜像
docker load -i all-images.tar
根据导出配置启动服务
docker-compose -f docker-compose-export.yml up -d
常用命令
镜像管理
拉取镜像
docker pull [image-url]
使用镜像源拉取(如轩辕镜像中mirror-url为docker.xuanyuan.me, path为library)
docker pull [mirror-url]/[path]/[image-name]:[image-version]
从本地归档文件(.tar)加载镜像到本地镜像库
docker load -i [image-name].tar
通过Shell重定向使用标准输入(stdin)加载镜像(适用于脚本或管道操作)
docker load < [image-name].tar
构建镜像
docker build -t [image-name] .
根据镜像ID重命名镜像名称
docker tag [image-id] [image-name]
运行镜像(使用-d以守护进程/后台方式运行)
docker run [image-name] .
查看所有镜像
docker image ls
docker images
上传镜像
docker push [image-url]
删除镜像
docker rmi [image-name] /
docker image rm [image-name]
从容器创建镜像
docker commit [container-name] [image-name]
保存为镜像文件
docker save -o [image-name].tar [image-name]
容器管理
创建容器
docker create [image-name]
启动运行并命名容器
docker run --name [container-name] [image-name]
例如运行一个基于Ubuntu的容器,可使用以下命令
docker run --name ubuntu_demo -itd docker.xuanyuan.me/library/ubuntu
(注:-itd为组合参数,-i使容器保持交互状态,-t为容器分配一个伪终端,-d在后台运行容器)
进入Ubuntu容器内部
docker exec -it ubuntu_demo /bin/bash
停止容器
docker stop [container-name]
删除容器
docker rm [container-name] /
docker container rm [container-name]
退出容器
exit
WSL配置Docker
通过WSL使用Docker时可能会缺失一些配置文件。
daemon.json
daemon.json是Docker引擎的配置管理文件,可以统一设置容器的网络、存储、安全、日志等选项。
docker安装后默认没有daemon.json这个配置文件,需要进行手动创建。在linux系统中,配置文件的默认径为:/etc/docker/daemon.json。
可使用以下命令手动创建daemon.json
sudo mkdir -p /etc/docker
sudo touch /etc/docker/daemon.json
sudo nano /etc/docker/daemon.json
例如配置docker镜像,可添加以下配置
1 2 3 4 5 6
| { "registry-mirrors": [ "https://<your-aliyun-id>.mirror.aliyuncs.com", "https://docker.xuanyuan.me" ] }
|
保存并退出后可使用以下命令重启docker
sudo systemctl restart docker
可使用docker info查看镜像配置是否生效,若仍未生效,可前往Docker Desktop手动设置。
右键点击任务栏Docker图标 → Settings → Docker Engine,修改JSON配置后点击Apply & Restart等待重启完成即可。
docker.service
docker.service文件是用于配置和管理Docker守护进程的systemd单元文件。它定义了Docker服务的启动、停止和重启行为,以及一些关键的配置参数。
如果缺失这个文件,在重启docker时会报错
Failed to start docker.service: Unit docker.service not found
可使用以下命令手动创建docker.service
sudo nano /etc/systemd/system/docker.service
可添加以下配置
1 2 3 4 5 6 7 8 9 10
| [Unit] Description=Docker Application Container Engine After=network-online.target
[Service] ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock Restart=always
[Install] WantedBy=multi-user.target
|
保存后使用以下命令重新加载systemd配置并启动服务
sudo systemctl daemon-reload
sudo systemctl start docker
参考文档