docker知识点
常用积累
| 操作 | 命令 |
|---|---|
| 拉取镜像 | docker pull [image] |
| 运行容器 | docker run -d -p [host_port]:[container_port] --name [name] [image] |
| 查看容器 | docker ps |
| 进入容器 | docker exec -it [container_name] /bin/bash |
| 查看日志 | docker logs [container_name] |
| 构建镜像 | docker build -t [image_name] . |
| 删除容器 | docker rm [container_name] |
| 删除镜像 | docker rmi [image_name] |
1 | docker默认需求root权限 |
1 | # 启动 Docker 服务 |
开发流程
| 步骤 | 操作 | 命令 |
|---|---|---|
| 1 | 编写 Dockerfile | vim Dockerfile |
| 2 | 构建镜像 | docker build -t my-app:1.0 . |
| 3 | 运行容器 | docker run -d -p 8080:3000 --name my-app-container my-app:1.0 |
| 4 | 查看运行状态 | docker ps |
| 5 | 访问应用 | curl http://localhost:8080 |
| 6 | 查看日志 | docker logs my-app-container |
1. 环境准备
1 | # 查看 Docker 服务 |
确保项目目录中包含以下内容:
- 项目源码(如
app.py、package.json、Dockerfile等)。 - 依赖文件(如
requirements.txt、package-lock.json)。
2. 编写 Dockerfile
如果只是需要一个简单的容器,如只需要python,就可以直接pull拉取镜像即可,但是复杂的镜像就需要自己通过Dockerfile进行构建镜像。
Dockerfile是一个文本文件,是构建镜像的蓝图,包含一系列指令(Instruction),用于定义构建 Docker 镜像的步骤。每个指令对应镜像的一层(Layer)。
核心指令
| 指令 | 作用 |
|---|---|
FROM |
指定基础镜像(必需)。 |
MAINTAINER |
指定镜像维护者(已弃用,推荐用 LABEL 替代)。 |
RUN |
在镜像中执行命令(构建时运行)。 |
CMD |
容器启动时默认执行的命令(可被 docker run 参数覆盖)。 |
ENTRYPOINT |
容器启动时执行的命令(不可被覆盖,可追加参数)。 |
COPY |
将本地文件复制到镜像中(不支持自动解压)。 |
ADD |
类似 COPY,但支持自动解压和远程 URL 下载。 |
WORKDIR |
设置工作目录(后续指令默认在此目录执行)。 |
ENV |
设置环境变量(持久化保存)。 |
EXPOSE |
声明容器监听的端口(需配合 docker run -p 使用)。 |
VOLUME |
创建挂载点(用于持久化数据)。 |
LABEL |
为镜像添加元数据(如作者、版本)。 |
USER |
指定后续指令运行的用户(提升安全性)。 |
HEALTHCHECK |
配置健康检查(监控容器状态)。 |
ARG |
定义构建时使用的变量(不保留在最终镜像中)。 |
编写步骤
选择基础镜像
FROM ubuntu:22.04 建议: 优先使用官方镜像(如 nginx、python、openjdk)。 使用轻量版镜像(如 alpine 或 slim)以减少体积。1
2
3
4
5
6
7
- 设置工作目录
- ```
WORKDIR /app
作用:后续指令默认在此目录下执行。安装依赖
RUN apt update && apt install -y python3 优化技巧: 合并多个 RUN 指令以减少镜像层数,如下: RUN apt update && \ apt install -y python3 && \ apt install -y curl1
2
3
4
5
6
7
8
9
- 复制文件到镜像
- ```
COPY . /app
区别:
COPY:直接复制文件(推荐用于本地文件)。
ADD:支持自动解压和远程 URL(谨慎使用,避免意外行为)。设置环境变量
ENV PATH="/app/bin:${PATH}"1
2
3
4
5
6
7
- 暴露端口
- ```
EXPOSE 80
注意:需在运行容器时用 -p 映射端口(如 docker run -p 8080:80)。定义启动命令
CMD ["python", "app.py"] 与 ENTRYPOINT 的区别: CMD 可被 docker run 参数覆盖,适合默认命令。 ENTRYPOINT 不可被覆盖,适合固定入口程序(如 Java 应用)。.git *.log __pycache__1
2
3
4
5
6
7
#### 技巧
**1. 使用 `.dockerignore` 文件**
- **作用**:排除不需要的文件,减少镜像体积。# 第一阶段:构建 FROM maven:3.8.6-jdk-17 AS build WORKDIR /app COPY pom.xml . RUN mvn dependency:go-offline COPY src ./src RUN mvn package # 第二阶段:运行 FROM openjdk:17-jdk-slim WORKDIR /app COPY --from=build /app/target/app.jar app.jar CMD ["java", "-jar", "app.jar"]1
2
3
4
5
**2. 多阶段构建(Multi-Stage Build)**
- **场景**:减少最终镜像体积(适合编译型语言如 Java、Go)。RUN useradd -m myuser USER myuser1
2
3
4
5
**3. 使用非 Root 用户**
- 安全建议:避免以root用户运行容器。ENV DATABASE_URL=postgresql://user:pass@host/db1
2
3
4
5
**4. 避免敏感信息硬编码**
- 方法:通过环境变量或docker run -e 传递敏感信息(如数据库密码)。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
**5. 保持镜像轻量化**
- 技巧:
- 使用 `alpine` 或 `slim` 版本的基础镜像。
- 删除不必要的依赖包(如 `apt clean`)。
#### 示例
**Java项目**
```dockerfile
# 构建阶段
FROM maven:3.8.6-jdk-17 AS build
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn package
# 运行阶段
FROM openjdk:17-jdk-slim
WORKDIR /app
COPY --from=build /app/target/app.jar app.jar
CMD ["java", "-jar", "app.jar"]
Node.js 项目
1 | # 使用 Node.js 16.x 镜像作为基础 |
一般还会编写的一个.dockerignore
1 | node_modules |
Python 项目
1 | # 使用 Python 3.9 镜像作为基础 |
ubuntu项目
1 | # 拉取基于官方的Ubuntu镜像 |
3. 构建镜像
1 | 在项目根目录下执行以下命令: |
my-app:1.0:自定义的镜像名和标签。.:表示 Dockerfile 所在的当前目录。
4. 运行容器
使用构建好的镜像启动容器:
1 | # 后台运行容器,映射端口(8080:容器端口) |
-d:后台运行。-p:映射端口(主机端口:容器端口)。--name:指定容器名称。-v:将本地的配置文件目录挂载到容器中。
5. 验证部署
5.1 查看运行中的容器
1 | docker ps |
输出示例:
1 | CONTAINER ID IMAGE COMMAND PORTS NAMES |
5.2 访问应用
通过浏览器或命令行访问应用:
1 | curl http://localhost:8080 |
5.3 查看容器日志
1 | docker logs 容器名称 |
5.4 查看容器具体信息
1 | docker inspect 容器名称 |
6. 清理资源
删除容器
1 | docker rm my-app-container |
删除镜像
1 | docker rmi my-app:1.0 |
清理无用资源
1 | # 删除所有停止的容器、未使用的镜像、网络等 |
7. 多容器协作Docker Compose
如果项目需要多个服务(如数据库、缓存),可以使用 docker-compose.yml 管理:
1 | version: '3' |
启动所有服务:
1 | docker-compose up -d |
镜像管理
拉取/推送镜像
1 | # 拉取最新版 Ubuntu 镜像 |
列出本地镜像
1 | # 列出所有本地镜像 |
删除镜像
1 | # 删除指定镜像(通过镜像名或 ID) |
构建镜像
1 | 在项目根目录下执行以下命令: |
my-app:1.0:自定义的镜像名和标签。.:表示 Dockerfile 所在的当前目录。
保存/加载镜像
1 | docker load # 将打包的镜像压缩文件加载到本地镜像库 |
容器管理
运行容器
1 | # 交互式运行 Ubuntu 容器并进入终端 |
查看运行中的容器
1 | # 查看所有运行中的容器 |
停止/启动/重启容器
1 | # 停止容器(通过容器名或 ID) |
删除容器
1 | # 删除已停止的容器 |
容器调试
进入运行中的容器
1 | # 进入容器的 Bash 终端 |
查看容器日志
1 | # 查看容器日志(默认显示最新日志) |
构建和运行自定义镜像
编写 Dockerfile
在项目根目录创建 Dockerfile,示例:
1 | nodejs项目 |
实例Java项目
1 | 如简单场景的单阶段构建Dockerfile编写 |
构建镜像
1 | # 构建镜像并打标签(-t 指定镜像名和标签,替换 your-image-name 为实际名称) |
运行自定义镜像
1 | # 后台运行容器,映射宿主机 8080 端口到容器 8080 端口 |
可选参数配置
1 | 挂载配置文件(动态更新配置): |
查看运行状态
1 | docker ps |
网络
1 | 默认情况下,所有容器都是以bridge方式连接到Docker的一个虚拟网桥上。 |
挂载代码目录(实时同步)
1 | # 运行容器时挂载宿主机代码目录到容器内 |
- 修改宿主机的代码会自动同步到容器中,无需重新构建镜像。
环境变量注入
1 | # 运行容器时设置环境变量 |
Docker Compose使用
Docker Compose 是 Docker 的一个子项目,用于定义和运行多容器 Docker 应用程序。通过一个 YAML 文件(docker-compose.yml)配置应用程序的多个服务及其依赖关系,然后使用一条命令启动、停止或管理整个应用栈。
创建 docker-compose.yml 文件:
1 | version: '3' |
1 | 启动所有服务:docker-compose up -d |
示例,多容器
1 | version: "3" |
常用命令
| 命令 | 说明 |
|---|---|
docker-compose up |
启动所有服务(默认前台运行,加 -d 后台运行) |
docker-compose down |
停止并删除容器、网络和卷 |
docker-compose ps |
列出当前运行的容器 |
docker-compose logs |
查看服务日志(加 -f 实时跟踪) |
docker-compose build |
构建服务镜像(需在 docker-compose.yml 中定义 build) |
docker-compose exec <service> <cmd> |
在运行中的容器中执行命令(如进入 shell) |
docker-compose restart <service> |
重启服务 |
docker-compose stop <service> |
停止服务 |
docker-compose start <service> |
启动已停止的服务 |
docker-compose pause <service> |
暂停服务 |
docker-compose unpause <service> |
恢复服务 |
注意事项
1. 多环境配置
使用
docker-compose.override.yml重写默认配置(适用于开发环境)。示例:
1
2
3
4
5# docker-compose.override.yml
services:
db:
environment:
MYSQL_ROOT_PASSWORD: dev_password
2. 生产环境优化
资源限制:
1
2
3
4
5
6
7services:
web:
deploy:
resources:
limits:
cpus: "0.5"
memory: 512M健康检查:
1
2
3
4
5
6
7services:
db:
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 3
清理资源
1 | # 清理所有停止的容器、未使用的镜像、网络等 |
推送镜像到远程仓库
1 | 登录 Docker Hub |
构建上下文
构建上下文(Build Context)是 Docker 构建镜像时的核心概念之一,它决定了 Docker 在构建过程中可以访问哪些文件和目录。
1. 构建上下文的定义
构建上下文是 Docker 客户端在执行 docker build 命令时,发送给 Docker 服务端(Docker 引擎)的一组文件和目录的集合。这些文件包括:
- Dockerfile:定义镜像构建步骤的文件。
- 其他资源文件:Dockerfile 中引用的文件(如代码、配置文件、依赖包等)。
Docker 会将这些文件打包成一个 tar 包,并发送到 Docker 服务端进行镜像构建。因此,Dockerfile 中引用的任何文件必须位于构建上下文中,否则构建会失败。
2. 如何指定构建上下文
构建上下文的指定是通过 docker build 命令的最后一个参数完成的。该参数可以是以下几种形式:
本地目录路径(最常见):
- 示例:
docker build -t myimage:latest . .表示当前目录作为构建上下文。- Docker 会将当前目录及其所有子目录中的文件打包并发送到 Docker 服务端。
- 示例:
远程 Git 仓库:
示例:
docker build https://github.com/user/repo.gitDocker 会克隆 Git 仓库并将其作为构建上下文。
可以通过
#<branch>:<subdir>指定分支和子目录:1
docker build https://github.com/user/repo.git#main:subdir
远程压缩包(tarball):
- 示例:
docker build https://example.com/context.tar.gz - Docker 会下载并解压该压缩包作为构建上下文。
- 示例:
纯文本文件(通过管道传递 Dockerfile):
- 示例:
cat Dockerfile | docker build - - 将 Dockerfile 通过管道传递给 Docker,此时构建上下文为空(仅包含 Dockerfile)。
- 示例:
3. 构建上下文的作用
- 资源访问范围:Dockerfile 中的指令(如
COPY、ADD)只能引用构建上下文内的文件。- 例如,如果 Dockerfile 中有
COPY app.py .,则app.py必须位于构建上下文目录中。
- 例如,如果 Dockerfile 中有
- 构建缓存:Docker 会基于构建上下文的内容判断是否可以使用缓存,提升构建速度。
- 安全性:构建上下文的范围直接影响发送到 Docker 服务端的数据量,避免敏感信息泄露。
4. 构建上下文的常见参数
(1) -f 或 --file
作用:指定 Dockerfile 的路径(默认为当前目录下的
Dockerfile)。示例:
1
docker build -f /path/to/Dockerfile -t myimage:latest .
注意事项:
- Dockerfile 必须位于构建上下文目录内。
- 如果 Dockerfile 位于子目录,需要调整路径(如
-f ./subdir/Dockerfile)。
(2) .(当前目录)
作用:表示当前目录作为构建上下文。
示例:
1
docker build -t myimage:latest .
隐含操作:
- Docker 会将当前目录及其所有子目录打包发送到 Docker 服务端。
- 如果目录过大,可能会显著影响构建速度。
(3) .dockerignore 文件
作用:排除不需要发送到 Docker 服务端的文件或目录,优化构建上下文大小。
示例:
1
2
3
4# .dockerignore
*.log
__pycache__
.git语法:
- 支持通配符(如
*.tmp)和目录路径(如node_modules/)。 - 类似
.gitignore的语法。
- 支持通配符(如
(4) --compress
作用:使用 gzip 压缩构建上下文,减少传输时间。
示例:
1
docker build --compress -t myimage:latest .
(5) --build-arg
作用:传递构建时变量(Build Args)给 Dockerfile。
示例:
1
docker build --build-arg VERSION=1.0 -t myimage:latest .
Dockerfile 中使用:
1
2ARG VERSION
RUN echo "Building version $VERSION"
(6) --target
作用:指定多阶段构建中的目标阶段(Target Stage)。
示例:
1
docker build --target builder -t myimage:builder .
Dockerfile 示例:
1
2
3
4
5
6
7
8FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp
FROM alpine:latest
COPY --from=builder /app/myapp /usr/local/bin/
CMD ["myapp"]
5. 构建上下文的优化建议
避免使用根目录作为上下文:
- 示例:
docker build -t myimage:latest /会导致发送整个硬盘内容,极不安全且低效。 - 推荐:将 Dockerfile 放在项目根目录,并确保上下文仅包含必要文件。
- 示例:
使用
.dockerignore文件:排除大型文件(如
node_modules/、.git/、*.log)。示例
.dockerignore内容:1
2
3node_modules/
.git/
*.log
多阶段构建:
减少最终镜像的大小,避免将构建工具(如
go、npm)打包到最终镜像中。示例:
1
2
3
4
5
6
7
8FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp
FROM alpine:latest
COPY --from=builder /app/myapp /usr/local/bin/
CMD ["myapp"]
分层构建:
- 通过合理设计 Dockerfile 层,利用缓存加速后续构建。
6. 常见问题与解决方案
Q1: 构建失败提示“no such file or directory”
- 原因:Dockerfile 中引用的文件不在构建上下文中。
- 解决方案:
- 确保文件位于构建上下文目录内。
- 或者调整 Dockerfile 中的路径(如
COPY ../subdir/file .)。
Q2: 构建上下文过大导致速度慢
- 原因:未使用
.dockerignore文件,或者包含不必要的文件。 - 解决方案:
- 创建
.dockerignore文件,排除无关文件。 - 将项目拆分为更小的模块,分别构建镜像。
- 创建
Q3: 如何指定远程 Git 仓库的子目录作为上下文?
示例:
1
docker build https://github.com/user/repo.git#main:subdir
main是 Git 分支,subdir是仓库中的子目录。
7. 总结
构建上下文是 Docker 构建镜像的基础,正确指定和管理上下文可以显著提升构建效率和安全性。以下是关键点总结:
- 上下文路径:通过
docker build命令的最后一个参数指定(如.表示当前目录)。 - Dockerfile 位置:使用
-f参数指定 Dockerfile 的路径。 - 优化工具:使用
.dockerignore文件、--compress参数和多阶段构建。 - 注意事项:避免使用根目录作为上下文,确保 Dockerfile 和所需文件位于上下文中。
通过合理配置构建上下文,可以高效地构建出符合需求的 Docker 镜像。
前言
什么是Docker
概述:Docker 是一个开源的应用容器引擎,它允许开发者将应用及其依赖打包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。Docker 的核心理念是“构建一次,随处运行”(通过 容器化技术 实现),这意味着开发者可以将应用和所有依赖项打包到一个容器中,这个容器可以在任何支持 Docker 的环境中运行,无论是在本地开发机器、测试服务器还是生产服务器上。
简单来说:一个快速构建,运行,管理应用的工具。
需求与解决
传统部署的痛点:应用程序依赖环境复杂(不同操作系统、库版本、配置),导致“在我的机器上能运行”(Works on My Machine)的问题。
虚拟化技术的局限:虚拟机(VM)需要完整的操作系统,资源占用高,启动慢,且难以快速扩展。
Docker 的解决方案:基于 Linux 内核技术(如
namespaces和cgroups),提供轻量级的进程隔离,实现高效的资源利用。微服务似然具备各种各样的优势,但是服务的拆分通常给部署带来了很大的麻烦
- 分布式系统中,依赖的组件非常多,不同组件之间部署时,往往会产生一些冲突
- 在数百,数千台服务中重复部署,环境不一定一直,会遇到各种问题
大型项目组件比较多,运行环境也较为复杂,部署时会碰到一些问题
- 依赖关系复杂,容易出现兼容性问题
- 开发、测试、生产环境有差异
例如一个项目中,部署时需要依赖于node.js、Redis、RabbitM、MySQL等,这些服务部署时所需要的函数库、依赖项各不相同,审核会有冲突,给部署带来了极大的困难
Docker解决依赖兼容问题
- 而Docker确巧妙的解决了这些问题,那么Docker是如何实现的呢?
- Docker为了解决依赖的兼容问题,采用了两个手段
- 将应用的函数库(libs)、依赖(Deps)、配置与应用一起打包
- 将每个应用放到一个隔离容器去运行,避免相互干扰
- 这样打包好的应用中,既包含了应用本身,也包含了应用所需要用到的函数库和依赖,无序在操作系统上安装这些,自然也就不存在不同应用之间的兼容问题了
- 虽然解决了不同应用的兼容问题,但是开发、测试等环节会存在差异,操作系统版本也会有差异,这些问题又该如何解决呢?
Docker解决操作系统环境差异
- 要解决不同操作系统环境差异问题,必须先了解操作系统结构,以一个Ubuntu操作系统为例,结构如下
- 系统应用:操作系统本身提供的应用、函数库。这些含数据是对内核指令的封装,使用更加方便
- 系统内核:所有Linux发行版的内核都是Linux,例如CentOS、Ubuntu、Fedora等。内核可以与计算机硬件交互,对外提供内核指令,用于操作计算机硬件。
- 计算机硬件:例如CPU、内存、磁盘等
- 应用于计算机交互的流程如下
- 应用调用操作系统应用(函数库),实现各种功能
- 系统函数库是对内核指令集的封装,会调用内核指令
- 内核指令操作计算机硬件
- Ubuntu和CentOS都是基于Linux内核,无非是系统应用不同,提供的函数库有差异
- 此时,如果将一个Ubuntu版本的MySQL应用安装到CentOS系统,MySQL在调用Ubuntu函数库时,会发现找不到或不匹配,就会报错
- Docker如何解决不同系统环境的问题?
- Docker将用户程序所需要的系统(比如Ubuntu)函数库一起打包
- Docker运行到不同操作系统时,直接基于打包的函数库,借助于操作系统的Linux内核来运行
小结
- Docker如何解决大型项目依赖关系复杂,不同组件依赖的兼容性问题?
- Docker允许开发中将应用、依赖、函数库、配置一起
打包,形成可移植镜像 - Docker应用运行在容器中,使用沙箱机制,相互
隔离
- Docker允许开发中将应用、依赖、函数库、配置一起
- Docker如何解决开发、测试、生产环境有差异的问题?
- Docker景象中包含完整运行环境,包括系统函数库,仅依赖系统的Linux内核,因此可以在任意Linux操作系统上运行
- Docker是一个快速交付应用、运行应用的技术,具备以下优势
- 可将程序及其依赖、运行环境一起打包为一个镜像,可以迁移到任意Linux操作系统
- 运行时利用沙箱机制形成隔离容器,各个应用互不干扰
- 启动、移除都可以通过一行命令完成,方便快捷
工作原理
Docker 通过 Linux 内核技术 实现容器化:
核心技术
- Namespace(命名空间):
- PID:进程隔离,每个容器有独立的进程树。
- NET:独立的网络栈(IP、端口)。
- Mount:隔离的文件系统。
- UTS:独立的主机名和域名。
- IPC:独立的进程间通信资源。
- User:用户和用户组隔离。
- Cgroups(控制组):
- 限制、记录和隔离进程组的资源(CPU、内存、磁盘 I/O 等)。
容器启动流程
- 用户运行
docker run命令。 - Docker 守护进程检查本地镜像,若不存在则从仓库拉取。
- 根据镜像创建容器,并分配资源(如网络、存储)。
- 启动容器内的主进程(通过
CMD或ENTRYPOINT定义的命令)。 - 用户可通过
docker exec与容器交互。
Docker与虚拟机的区别
- Docker可以让一个应用在任何操作系统中都十分方便的运行,而我们以前接触的虚拟机,也能在一个操作系统中,运行另外一个操作系统,保护系统中的任何应用
- 二者有什么差异呢?
- 虚拟机(virtual machine)是在操作系统中
模拟硬件设备,然后运行另一个操作系统。例如在Windows系统中运行CentOS系统,就可以运行任意的CentOS应用了 - Docker仅仅是封装函数库,并没有模拟完整的操作系统
- 虚拟机(virtual machine)是在操作系统中
| 特性 | Docker 容器 | 虚拟机(VM) |
|---|---|---|
| 资源占用 | 轻量级(共享内核) | 重型(需完整 OS) |
| 启动时间 | 秒级 | 分钟级 |
| 隔离性 | 进程级隔离 | 硬件级隔离 |
| 适用场景 | 单应用或多进程应用 | 需完整 OS 环境的复杂应用 |
| 典型用途 | 微服务、CI/CD、开发环境 | 跨平台虚拟化、隔离敏感应用 |
Docker架构
Docker 的架构为 客户端-服务器(C/S)模式。
- 服务端(server):Docker守护进程,负责处理Docker指令,管理镜像、容器等
- 客户端(client):通过命令或RestAPI向Docker服务端发送指令,可以在本地或远程向服务端发送指令
核心组件如下:
客户端Client
- 用户通过命令行(
docker命令)或 GUI 工具与 Docker 守护进程通信。
守护进程Daemon
- 运行在宿主机上的后台服务,负责:
- 构建、运行、监控容器。
- 管理镜像、网络和存储卷。
- 处理来自客户端的请求。
镜像仓库Registry
- 存储和分发镜像的中心,典型如 Docker Hub。
- 私有仓库:企业常用 Harbor、GitLab Registry 等。
容器与镜像
- 镜像(Image):Docker将应用程序及其所需要的依赖、函数库、环境、配置等文件打包在一起,称为镜像
- 容器(Container):镜像中的应用程序形成后的进程就是
容器,只是Docker会给容器进程做隔离,对外不可见。镜像运行起来就是容器,一个镜像可以运行多个容器。 - 一切应用最终都是代码组成,都是硬盘中的一个个字节形成的文件,只有运行时,才会加载到内存,形成进程
- 而
镜像,就是吧一个应用在硬盘上的文件、机器运行环境、部分系统函数库文件一起打包成的文件包。这个文件包是只读的(防止你对镜像文件进行修改/污染,从而导致镜像不可用,容器从镜像中拷贝一份文件到自己的空间里来写数据) - 而
容器呢,就是把这些文件中编写的程序、函数加载到内存中允许形成进程,只不过要隔离起来。因此一个镜像可以启动多次,形成多个容器进程。
DockerHub
一个镜像托管的服务器,类似的还有阿里云镜像服务,统称为DockerRegistry
开源应用程序非常多,打包这些应用往往都是重复性劳动,为了避免这些重复劳动,人们就会将自己打包的应用镜像,例如Redis、MySQL镜像放到网络上来共享使用,就像GitHub的代码共享一样
我们一方面可以将自己的镜像共享到DockerHub,另一方面也可以从DockerHub拉取镜像
生态系统
- Docker Compose:通过 YAML 文件定义多容器应用(如微服务)。
- Docker Swarm:轻量级容器集群管理工具。
- Kubernetes:更强大的容器编排系统(Docker 容器是其基础)。
- Docker Hub:全球最大容器镜像仓库。
- Portainer:图形化管理 Docker 环境。
安装Docker
- Docker 分为 CE 和 EE 两大版本。CE 即社区版,免费,支持周期 7 个月;EE 即企业版,强调安全,付费使用,支持周期 24 个月。
- Docker CE 分为
stabletest和nightly三个更新频道。 - 官方网站上有各种环境下的 安装指南,这里主要介绍 Docker CE 在 CentOS上的安装。
- Docker CE 支持 64 位版本 CentOS 7,并且要求内核版本不低于 3.10
CentOS 7满足最低内核要求,本文也是在CentOS 7安装Docker
卸载(可选)
- 如果之前安装过旧版本的Docker,可以使用下面命令卸载
1 | yum remove docker \ |
安装Docker
- 首先先安装yum工具
1 | bash |
- 然后更新本地镜像源
1 | bash |
- 然后安装社区版Docker
1 | bash |
启动Docker
- Docker应用需要用到各种端口,挨个修改防火墙设置很麻烦,所以这里建议直接关闭防火墙
1 | bash |
- 通过命令启动Docker
1 | bash |
- 然后输入命令,查看docker版本
1 | bash |
结果
1 | bash |
配置镜像加速
- Docker官方镜像库网速较差,建议设置为国内镜像服务,参考阿里云的镜像加速文档:https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors
1 | # 创建目录 |
理论
镜像Image
定义:只读模板,包含运行应用程序所需的所有内容(代码、依赖、环境配置)。
当我们利用Docker安装应用时,Docker会 自动搜索并下载应用镜像(image) 。镜像不仅包含应用本身,还包含应用运行所需要的环境、配置、系统函数库。
特点:
- 分层结构:由多个只读层叠加而成(如基础 OS 层 + 应用层)。
- 可重复性:同一镜像在任何环境生成的容器行为一致。
创建方式:
- 通过
Dockerfile构建(定义构建步骤)。 - 从 Docker Hub 或私有仓库拉取现成镜像(如
nginx:latest)。
镜像名称
首先来看下镜像的名称组成:
- 镜像名称一般分为两部分:[repository]:[tag]
例如
mysql:5.7,这里的mysql就是repository,5.7就是tag,合在一起就是镜像名称,代表5.7版本的MySQL镜像- 在没有指定tag时,默认是latest,代表最新版本的镜像,例如
mysql:latest
容器(Container)
容器:Docker会在运行镜像时创建一个隔离环境,称为容器( container)。容器有自己独立的环境,比如独立的ip,网络等等,可以跨系统运行。
镜像仓库:Docker官方提供了一个专门管理、存储镜像的网站,并对外开放了镜像上传、下载的权利。https://hub.docker.com/
定义:镜像的运行实例,具有独立的文件系统、网络、进程空间。
特点:
- 动态:容器是运行时的实体,可启动、停止、删除。
- 轻量级:共享宿主机内核,无需虚拟化开销。
生命周期:从 docker run 启动到 docker stop 或 docker kill 结束。
容器保护三个状态
- 运行:进程正常运行
- 暂停:进程暂停,CPU不再运行,不释放内存
- 停止:进程终止,回收进程占用的内存、CPU等资源
暂停和停止的操作系统的处理方式不同,暂停是操作系统将容器内的进程挂起,容器关联的内存暂存起来,然后CPU不再执行这个进程,但是使用
unpause命令恢复,内存空间被恢复,程序继续运行。停止是直接将进程杀死,容器所占的内存回收,保存的仅剩容器的文件系统,也就是那些静态的资源
docker pause:让一个运行的容器暂停docker unpause:让一个容器从暂停状态恢复运行
案例一
- 创建并运行nginx容器的命令
1 | bash |
- 命令解读
docker run:创建并运行一个容器--name:给容器起一个名字,例如叫做myNginx-p:将宿主机端口与容器端口映射,冒号左侧是宿主机端口,右侧是容器端口-d:后台运行容器nginx:镜像名称,例如nginx
- 这里的
-p参数,是将容器端口映射到宿主机端口 - 默认情况下,容器是隔离环境,我们直接访问宿主机的80端口,肯定访问不到容器中的nginx
- 现在,容器的80端口和宿主机的80端口关联了起来,当我们访问宿主机的80端口时,就会被映射到容器的80端口,这样就能访问nginx了
- 那我们再浏览器输入虚拟机ip:80就能看到nginx默认页面了
案例二
需求:进入Nginx容器,修改HTML文件内容,添加Welcome To My Blog!
提示:进入容器要用到docker exec命令
1 | bash |
- 进入容器。进入刚刚我们创建好的nginx容器
1 | bash |
- 命令解读
docker exec:进入容器内部,执行一个命令-it:给当前进入的容器创建一个标准输入、输出终端,允许我们与容器交互myNginx:要进入的容器名称bash:进入容器后执行的命令,bash是一个linux终端交互命令
进入nginx的HTML所在目录
- 容器内部会模拟一个独立的Linux文件系统,看起来就如同一个linux服务器一样,nginx的环境、配置、运行文件全部都在这个文件系统中,包括我们要修改的html文件
- 查看DockerHub网站中的nginx页面,可以知道nginx的html目录位置在
/usr/share/nginx/html - 我们执行命令进入到该目录
1
2bash
cd /usr/share/nginx/html查看目录下的文件
1
2
3bash
root@310016c9b413:/usr/share/nginx/html## ls
50x.html index.html修改index.html的内容
- 容器内没有vi命令,无法直接修改,我们使用下面的命令来修改
1
2bash
sed -i -e 's#Welcome to nginx#Welcome To My Blog#g' index.html在浏览器访问自己的虚拟机ip:80,即可看到结果(80端口可以不写)
小结
docker run命令常见的参数有哪些?--name:指定容器名称-p:指定端口映射-d:让容器后台运行
- 查看容器日志的命令
docker logs- 添加
-f参数可以持续查看日志
- 查看容器状态:
docker psdocker ps -a查看所有容器,包括已停止的
现在是不是感觉修改文件好麻烦,因为没给提供vi命令,不能直接编辑,所以这就要用到我们下面说的数据卷了
仓库(Repository)
定义:集中存储镜像的场所,分为 公有仓库(如 Docker Hub)和 私有仓库(如 Harbor)。
作用:
- 共享镜像:开发者可上传或下载镜像。
- 版本管理:通过标签(Tag)区分不同版本(如
nginx:1.21和nginx:latest)。
数据卷
1 | 概述:数据卷(volume) 是一个虚拟目录,是容器内目录与宿主机目录之间映射的桥梁。 |
在之前的nginx案例中,修改nginx的html页面时,需要进入nginx内部。并且因为没有编译器,修改文件也很麻烦,这就是容器与数据(容器内文件)耦合带来的后果,如果我们另外运行一台新的nginx容器,那么这台新的nginx容器也不能直接使用我们修改好的html文件,具有很多缺点
- 不便于修改:当我们要修改nginx的html内容时,需要进入容器内部修改,很不方便
- 数据不可服用:由于容器内的修改对外是不可见的,所有的修改对新创建的容器也是不可复用的
- 升级维护困难:数据在容器内,如果要升级容器必然删除旧容器,那么旧容器中的所有数据也跟着被删除了(包括改好的html页面)
要解决这个问题,必须将数据和容器解耦,这就要用到数据卷了
数据卷(volume)是一个虚拟目录,指向宿主机文件系统中的某个目录
一旦完成数据卷挂载,对容器的一切操作都会作用在对应的宿主机目录了。这样我们操作宿主机的/var/lib/docker/volumes/html目录,就等同于操作容器内的/usr/share/nginx/html目录了
数据集操作命令
- 数据卷操作的基本语法如下
1 | bash |
docker volumecommand1
2
3
命令是数据卷操作,根据命令后跟随的bash docker volume create html1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
来确定下一步的操作
- `create`:创建一个volume
- `inspect`:显示一个或多个volume的信息
- `ls`:列出所有的volume
- `prune`:删除未使用的volume
- `rm`:删除一个或多个指定的volume
### 创建和查看数据集
需求:创建一个数据卷,并查看数据卷在宿主机的目录位置
1. 创建数据卷
1 |
|
bash
docker volume ls
1 |
|
bash
[root@localhost ~]## docker volume ls
DRIVER VOLUME NAME
local html
1 |
|
bash
docker volume inspect html
1 |
|
bash
[root@localhost ~]## docker volume inspect html
[
{
“CreatedAt”: “2022-12-19T12:51:54+08:00”,
“Driver”: “local”,
“Labels”: {},
“Mountpoint”: “/var/lib/docker/volumes/html/_data”,
“Name”: “html”,
“Options”: {},
“Scope”: “local”
}
]
1 |
|
前言:之前是创建数据卷进行挂载,数据卷的位置太深不方便找,现在可以在宿主机也就是本地任意目录进行挂载。
挂载本地目录
-v 本地目录:容器内目录
挂载本地文件
-v 本地文件:容器内文件
在执行docker run命令时,使用-v本地目录:容器内目录可以完成本地目录挂载
注意:
本地目录必须以”/” 或”./“开头,如果直接以名称开头,会被识别为数据卷而非本地目录
-v mysql:/var/lib/mysql会被识别为一个数据卷叫mysql
-v ./mysql:/var/lib/mysql会被识别为当前目录下的mysq|目录
例子:
mysql在创建并运行镜像的时候,会自动创建一个匿名卷与/var/lib/mysql(数据存储的文件)挂载。匿名卷的名字是一串hash值
docker inspect mysql //查看MySQL容器详细信息
一般mysql会与本地目录挂载,与数据文件,配置文件,初始化脚本挂载
①挂载/root/mysql/data到容器内的/var/lib/mysql目录
②挂载/root/mysql/init到容器内的/docker-entrypoint-initdb.d目录,本地目录下可以先放好的SQL脚本
③挂载/root/mysql/conf到容器内的/etc/mysql/conf.d目录,本地目录下可以先好的配置文件
先把本地目录创建好,然后执行如下命令
docker run -d
–name mysql
-p 3306:3306
-e TZ=Asia/Shanghai
-e MYSQL_ROOT_PASSWORD=123
-v ./mysql/data:/var/lib/mysql
-v ./mysql/conf:/etc/mysql/conf.d
-v ./mysql/init:/docker-entrypoint-initdb.d
mysql
1 |
|
docker run
– name myNginx
-v html:/root/html
-p 8080:80
nginx \
1 |
|
bash
docker run –name myNginx -v html:/usr/share/nginx/html -p 80:80 -d nginx
1 |
|
bash
查看数据卷位置
docker volume inspect html
进入该目录
cd /var/lib/docker/volumes/html/_data
修改文件
vi index.html
也可以在FinalShell中使用外部编译器(例如VSCode)来修改文件
1 |
|
bash
docker pull mysql
1 |
|
bash
mkdir -p /tmp/mysql/data
1 |
|
bash
mkdir -p /tmp/mysql/conf
1 |
|
bash
docker run
–name mysql \
-e MYSQL_ROOT_PASSWORD=root
-v /tmp/mysql/conf:/etc/mysql/conf.d
-v /tmp/mysql/data:/var/lib/mysql
-p 3306:3306
-d
mysql
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
3. 尝试使用Navicat连接数据库,注意自己设置的密码
### 小结
- `docker run`的命令中通过-v参数挂载文件或目录到容器中
- `-v [volume名称]:[容器内目录]`
- `-v [宿主机文件]:[容器内文件]`
- `-v [宿主机目录]:[容器内目录]`
- 数据卷挂载与目录直接挂载的区别
- 数据卷挂载耦合度低,由docker来管理目录,但是目录较深,不好找
- 目录挂载耦合度高,需要我们自己管理目录,不过目录容易寻找查看
## 网络
默认情况下,所有容器都是以bridge方式连接到Docker的一个虚拟网桥上。
docker会默认创建一个名为docker0的网卡,可以通过ip addr查看
加入自定义网络的容器才可以通过容器名互相访问
Docker的网络操作命令如下:
docker network create:创建一个网络
docker network 1s:查看所有网络
docker network rm:删除指定网络
docker network prune:清除未使用的网络
docker network connect:使指定容器连接加入某网络
docker network di sconnect:使指定容器连接离开某网络
docker network inspect:查看网络详细信息
1 |
|
概述:Dockerfile就是一个文本文件, 其中包含一个个的指令(Instruction), 用指令来说明要执行什么操作来构建镜像。将来Docker可以根据Dockerfile帮我们构建镜像。
作用:编辑相关语法的代码,实现自动打包镜像的功能
常见指令如下:
FROM:指定基础镜像。例如:FROM centos:6
ENV:设置环境变量,可在后面指令使用。例如:ENV key value
COPY:拷贝本地文件到镜像的指定目录。例如:COPY ./jrell.tar.gz /tmp
RUN:执行Linux的shell命令,一般是安装过程的命令。例如:RUN tar -zxvf /tmp/jre11.tar.gz && EXPORTS path=/tmp/jrell:$path
EXPOSE:指定容器运行时监听的端口,是给镜像使用者看的。例如:EXPOSE 8080
ENTRYPOINT:镜像中应用的启动命令,容器运行时调用。例如:ENTRYPOINT java -jar xx.jar
更多指令参考:https://dqcs.docker.com/engine/reference/builder
1 |
|
例如要基于Ubuntu镜像来构建一个Java应用,其Dockerfile内容如下:
指定基础镜像
FROM ubuntu:16.04
配置环境变量,JDK的安装目录、容器内时区
ENV JAVA_DIR=/usr/local
ENV TZ=Asia/Shanghai
拷贝jdk和java项目的包
COPY ./jdk8.tar.gz $JAVA_DIR/
COPY ./docker-demo.jar /tmp/app.jar
设定时区
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
安装JDK
RUN cd $JAVA_DIR
&& tar -xf ./jdk8.tar.gz
&& mv ./jdk1.8.0_144 ./java8
配置环境变量
ENV JAVA_HOME=$JAVA_DIR/java8
ENV PATH=$PATH:$JAVA_HOME/bin
指定项目监听的端口
EXPOSE 8080
入口,java项目的启动命令
ENTRYPOINT [“java”, “-jar”, “/app.jar”]
简化后:
基础镜像
FROM openjdk:11.0-jre-buster
设定时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
拷贝jar包
COPY docker-demo.jar /app.jar
入口
ENTRYPOINT [“java”, “-jar”, “/app.jar”]
1 |
|
bash
mkdir /tmp/docker-demo
1 |
|
bash
指定基础镜像
FROM ubuntu:16.04
配置环境变量,JDK的安装目录
ENV JAVA_DIR=/usr/local
拷贝jdk的到JAVA_DIR目录下
COPY ./jdk8.tar.gz $JAVA_DIR/
安装JDK
RUN cd $JAVA_DIR && tar -xf ./jdk8.tar.gz && mv ./jdk1.8.0_44 ./java8
配置环境变量
ENV JAVA_HOME=$JAVA_DIR/java8
ENV PATH=$PATH:$JAVA_HOME/bin
拷贝java项目的包到指定目录下,我这里是/tmp/app.jar
COPY ./docker-demo.jar /tmp/app.jar
暴露端口,注意这里是8090端口,如果你之前没有关闭防火墙,请关闭防火墙或打开对应端口,云服务器同理
EXPOSE 8090
入口,java项目的启动命令
ENTERPOINT java -jar /tmp/app.jar
1 |
|
bash
docker build -t docker_demo:1.0 .
1 |
|
bash
[root@localhost docker-demo]## docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
docker_demo 1.0 c8acd2dd02cf About a minute ago 722MB
redis latest 29ab4501eac3 2 days ago 117MB
nginx latest 3964ce7b8458 5 days ago 142MB
ubuntu 16.04 b6f507652425 15 months ago 135MB
mysql 5.7.25 98455b9624a9 3 years ago 372MB
1 |
|
bash
docker run –name testDemo -p 8090:8090 -d docker_demo:1.0
1 |
|
bash
将openjdk:8作为基础镜像
FROM openjdk:8
拷贝java项目的包到指定目录下,我这里是/tmp/app.jar
COPY ./docker-demo.jar /tmp/app.jar
暴露端口
EXPOSE 8090
入口
ENTRYPOINT java -jar /tmp/app.jar
1 |
|
bash
docker build -t docker_demo:2.0 .
1 |
|
bash
docker run –name testDemo02 -p 8090:8090 -d docker_demo:2.0
1 |
|
依赖管理
- 通过
depends_on确保服务按顺序启动(如数据库先于 Web 服务启动)。 - 示例:
1
2
3
4
5
6services:
web:
depends_on:
- db
db:
image: mysql:5.7
- 通过
网络配置
- 自动创建默认网络,服务可通过名称互相访问。
- 可自定义网络并指定 IP 地址。
- 示例:
1
2
3
4
5
6
7networks:
app-network:
driver: bridge
services:
web:
networks:
- app-network
卷挂载
- 挂载主机目录或 Docker 卷,实现数据持久化。
- 示例:
1
2volumes:
- ./data:/var/lib/mysql
使用流程
1. 创建 docker-compose.yml 文件
- 文件结构示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16version: '3.8' # Compose 文件版本
services:
web:
image: nginx:latest
ports:
- "80:80"
volumes:
- ./html:/usr/share/nginx/html
db:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: example
volumes:
- db-data:/var/lib/mysql
volumes:
db-data: # 定义数据卷
2. 启动服务
1 | # 启动所有服务(后台运行) |
3. 管理服务
- 查看运行状态:
1
docker-compose ps
- 停止服务:
1
docker-compose stop
- 删除容器、网络和卷:
1
docker-compose down
4. 多环境配置
- 使用
-f参数指定不同环境的配置文件:1
2
3
4
5# 开发环境
docker-compose -f docker-compose.yml -f docker-compose.dev.yml up
# 生产环境
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
概述:Docker Compose通过一个单独的docker-compose.yml模板文件(YAML格式)来定义一组相关联的应用容器,帮助我们实现多个相互关联的Docker容器的快速部署。
作用:做项目或者集群的一键部署。
- Docker Compose可以基于Compose文件帮我们快速地部署分布式英语,而无需手动一个个创建和运行容器
- 真实企业项目开发中,可能有几十个,
- 相比较于k8s,它更适合单机部署,测试环境,小型的多容器编排管理
1 | docker compose的命令格式如下: |
示例
1 | 例如部署一个java应用的dockercompose.yaml文件: |
安装DockerCompose
- 在Linux下使用命令下载
1 | bash |
- 修改文件权限
1 | bash |
- Base自动补全命令
1 | bash |
如出现错误Failed connect to raw.githubusercontent.com:443; Connection refused,需要修改自己的hosts文件
1 | bash |
如出现新错误TCP connection reset by peer,重复发起命令,多试几次
部署微服务集群
需求:将之前学习的cloud-demo微服务集群利用DockerCompose部署
- 实现思路
- 编写docker-compose文件
- 修改自己的cloud-demo项目,将其中的数据库、nacos地址,都重命名为docker-compose中的服务名
- 使用maven打包工具,将项目中的每个微服务都打包为app.jar(打包名与Dockerfile中一致即可)
- 将打包好的app.jar拷贝到cloud-demo中的每一个对应的子目录中,编写Dockerfile文件
- 将cloud-demo上传至虚拟机,利用
docker-compose up -d来部署
compose文件
- 针对我们之前写的cloud-demo,来编写对应的docker-compose文件
1 | yml |
- 其中包含了5个服务:
- nacos:作为注册中心和配置中心
- image: nacos/nacos-server:基于nacos/nacos-server镜像构建
- environment: 环境变量
- MODE: standalone:单点模式启动
- ports:端口映射,这里暴露了8848端口
- mysql:数据库
- image: mysql5.7.25:基于5.7.25版本的MySQL镜像构建
- environment:环境变量
- MYSQL_ROOT_PASSWORD: root:设置数据库root账户密码为root
- volumes:数据卷挂载,这里挂载了mysql的data和conf目录
- userservice:基于Dockerfile临时构建,userservice不需要暴露端口,网关才是微服务的入口,如果暴露了userservice的端口,那么网关的身份认证,权限校验就形同虚设了
- orderservice:基于Dockerfile临时构建,不需要暴露端口,理由同上
- gateway:基于Dockerfile临时构建,网关需要暴露端口,它是其他微服务的入口
- nacos:作为注册中心和配置中心
修改微服务配置
使用Docker Compose部署时,所有的服务之间都可以用服务名互相访问,那我们现在就需要修改我们cloud-demo中的yml配置文件,如下
bootstrap.yml
application.yml
1 | yml |
打包
- 将我们修改好的代码打包,注意修改pom文件指定打包名为app
1 | xml |
- 之后使用maven工具打包
拷贝jar包到部署目录,并编写Dockerfile文件
- gateway
- order-service
- user-service
1 | yml |
- 最终的目录结构如下
- cloud-demo
- gateway
- app.jar
- Dockerfile.yml
- order-service
- app.jar
- Dockerfile.yml
- user-service
- app.jar
- Dockerfile.yml
- mysql
- data
- conf
- docker-compose.yml
- gateway
- cloud-demo
部署
- 将cloud-demo上传到虚拟机,进入目录,执行以下命令
1 | bash |
- 启动之后查看日志,会发现日志中报错
com.alibaba.nacos.api.exception.NacosException: failed to req API:/nacos/v1/ns/instance/list after all servers([nacos:8848]) tried: java.net.ConnectException: Connection refused (Connection refused)
1 | bash |
阿里巴巴nacos连接失败,其原因是userservice在nacos之前启动了,而nacos启动太慢了,userservice注册失败,而且也没有重试机制(等nacos启动完成后,重试注册,就可以避免这个问题)
- 所以建议nacos单独先启动,其他服务后启动,我这里的解决方案是重启另外三个服务
- 重启gateway userservice orderservice服务
1 | bash |
- 查看userservice启动日志,这次就不报错了
1 | bash |
- 打开浏览器访问http://192.168.128.130:10010/user/1?authorization=admin, 也可以看到数据
Docker镜像仓库
- 搭建镜像仓库可以基于Docker官方提供的DockerRegistry来实现
- 官网地址:https://hub.docker.com/_/registry
搭建私有镜像仓库
- 我们自己编写的项目显然是不适合放到Docker的共有仓库的,所以需要我们搭建一个私服
配置Docker信任地址
- 我们的私服采用的是http协议,默认不被Docker信任,所以需要做一个配置:
1 | bash |
带图形化界面版本
- 使用DockerCompose部署带有图象界面的DockerRegistry,命令如下:
1 | yml |
- 随后打开浏览器访问http://192.168.128.130:8080/, 就能看到带图形化界面的镜像仓库了
推送、拉取镜像
推送镜像到私有镜像服务必须先tag,步骤如下
- 重新tag本地镜像,名称前缀为私有仓库的地址:192.168.128.130:8080/
1
2bash
docker tag nginx:latest 192.168.128.130:8080/nginx:1.0- 推送镜像
1
2bash
docker push 192.168.128.130:8080/nginx:1.0- 拉取镜像
1
2bash
docker pull 192.168.128.130:8080/nginx:1.0
总结
- 作为程序员我们应
怎样理解docker?- 容器技术的
起源- 假设公司正在秘密研发下一个“今日头条”APP,我们姑且称为明日头条,程序员自己从头到尾搭建了一套环境开始写代码,写完代码后程序员要把代码交给测试同学测试,这时测试同学开始从头到尾搭建这套环境,测试过程中出现问题程序员也不用担心,大可以一脸无辜的撒娇,“明明在人家的环境上可以运行的”。
- 测试同学测完后终于可以上线了,这时运维同学又要重新从头到尾搭建这套环境,费了九牛二虎之力搭建好环境开始上线,糟糕,上线系统就崩溃了,这时心理素质好的程序员又可以施展演技了,“明明在人家的环境上可以运行的”。
- 从整个过程可以看到,不但我们重复搭建了三套环境还要迫使程序员转行演员浪费表演才华,典型的浪费时间和效率,聪明的程序员是永远不会满足现状的,因此又到了程序员改变世界的时候了,容器技术应运而生。
- 有的同学可能会说:“等等,先别改变世界,我们有虚拟机啊,VMware好用的飞起,先搭好一套虚拟机环境然后给测试和运维clone出来不就可以了吗?”
- 在没有容器技术之前,这确实是一个好办法,只不过这个办法还
没有那么好。- 先科普一下,现在云计算其底层的基石就是虚拟机技术,云计算厂商买回来一堆硬件搭建好数据中心后使用虚拟机技术就可以将硬件资源进行切分了,比如可以切分出100台虚拟机,这样就可以卖给很多用户了。
- 那么这个办法
为什么不好呢?- 因为操作系统
太笨重了,操作系统运行起来是需要占用很多资源的,大家对此肯定深有体会,刚装好的系统还什么都没有部署,单纯的操作系统其磁盘占用至少几十G起步,内存要几个G起步。 - 我们需要的只是一个简单的应用程序,不需要把内存浪费在对我们应用程序
无用的操作系统上。 - 此外,还有启动时间的问题,我们知道操作系统重启是非常慢的,因为操作系统要从头到尾把该检测的都检测了,该加载的都加载上,这个过程非常缓慢,动辄就得几分钟,所以操作系统终究还是太笨重了
- 因为操作系统
- 容器技术的
- 那么有没有一种技术可以让我们获得虚拟机的好处又能克服这些缺点从而一举
实现鱼和熊掌的兼得呢?- 答案是肯定的,这就是
容器技术。
- 答案是肯定的,这就是
什么是容器- 容器一词的英文是container,其实container还有集装箱的意思,集装箱绝对是商业史上了不起的一项发明,大大降低了海洋贸易运输成本。让我们来看看集装箱的好处:
- 集装箱之间相互隔离
- 长期反复使用
- 快速装载和卸载
- 规格标准,在港口和船上都可以摆放
- 回到软件中的容器,其实容器和集装箱在概念上是很相似的。
- 现代软件开发的一大目的就是隔离,应用程序在运行时相互独立互不干扰,这种隔离实现起来是很不容易的,其中一种解决方案就是上面提到的虚拟机技术,通过将应用程序部署在不同的虚拟机中从而实现隔离。
- 但是虚拟机技术有上述提到的各种缺点,那么容器技术又怎么样呢?
- 与虚拟机通过操作系统实现隔离不同,容器技术只隔离应用程序的运行时环境但容器之间可以共享同一个操作系统,这里的运行时环境指的是程序运行依赖的各种库以及配置。
- 与操作系统动辄几G的内存占用相比,容器技术只需数M空间,因此我们可以在同样规格的硬件上大量部署容器,这是虚拟机所不能比拟的,而且不同于操作系统数分钟的启动时间容器几乎瞬时启动,容器技术为打包服务栈提供了一种更加高效的方式。
- 容器一词的英文是container,其实container还有集装箱的意思,集装箱绝对是商业史上了不起的一项发明,大大降低了海洋贸易运输成本。让我们来看看集装箱的好处:
那么我们该怎么使用容器呢?这就要讲到docker了。- 注意,容器是一种通用技术,docker只是其中的一种实现。
什么是docker- docker是一个用Go语言实现的开源项目,可以让我们方便的创建和使用容器,docker将程序以及程序所有的依赖都打包到docker container,这样你的程序可以在任何环境都会有一致的表现,这里程序运行的依赖也就是容器就好比集装箱,容器所处的操作系统环境就好比货船或港口,程序的表现只和集装箱有关系(容器),和集装箱放在哪个货船或者哪个港口(操作系统)没有关系。
- 因此我们可以看到docker可以屏蔽环境差异,也就是说,只要你的程序打包到了docker中,那么无论运行在什么环境下程序的行为都是一致的,程序员再也无法施展表演才华了,不会再有“在我的环境上可以运行”,真正实现“build once, run everywhere”。
- 此外docker的另一个好处就是快速部署,这是当前互联网公司最常见的一个应用场景,一个原因在于容器启动速度非常快,另一个原因在于只要确保一个容器中的程序正确运行,那么你就能确信无论在生产环境部署多少都能正确运行。
如何使用docker- docker中有这样几个概念:
- dockerfile
- image
- container
- 实际上你可以简单的把image理解为可执行程序,container就是运行起来的进程。
- 那么写程序需要源代码,那么“写”image就需要dockerfile,dockerfile就是image的源代码,docker就是”编译器”。
- 因此我们只需要在dockerfile中指定需要哪些程序、依赖什么样的配置,之后把dockerfile交给“编译器”docker进行“编译”,也就是docker build命令,生成的可执行程序就是image,之后就可以运行这个image了,这就是docker run命令,image运行起来后就是docker container。
- 具体的使用方法就不再这里赘述了,大家可以参考docker的官方文档,那里有更详细的讲解。
- docker中有这样几个概念:
docker是如何工作的- 实际上docker使用了常见的CS架构,也就是client-server模式,docker client负责处理用户输入的各种命令,比如docker build、docker run,真正工作的其实是server,也就是docker demon,值得注意的是,docker client和docker demon可以运行在同一台机器上。
接下来我们用几个命令来讲解一下docker的工作流程:- docker build
- 当我们写完dockerfile交给docker“编译”时使用这个命令,那么client在接收到请求后转发给docker daemon,接着docker daemon根据dockerfile创建出“可执行程序”image。
- docker run
- 有了“可执行程序”image后就可以运行程序了,接下来使用命令docker run,docker daemon接收到该命令后找到具体的image,然后加载到内存开始执行,image执行起来就是所谓的container。
- docker pull
- 其实docker build和docker run是两个最核心的命令,会用这两个命令基本上docker就可以用起来了,剩下的就是一些补充。
- 那么docker pull是什么意思呢?
- 我们之前说过,docker中image的概念就类似于“可执行程序”,我们可以从哪里下载到别人写好的应用程序呢?很简单,那就是APP Store,即应用商店。与之类似,既然image也是一种“可执行程序”,那么有没有”Docker Image Store”呢?答案是肯定的,这就是Docker Hub,docker官方的“应用商店”,你可以在这里下载到别人编写好的image,这样你就不用自己编写dockerfile了。
- docker registry 可以用来存放各种image,公共的可以供任何人下载image的仓库就是docker Hub。那么该怎么从Docker Hub中下载image呢,就是这里的docker pull命令了。
- 因此,这个命令的实现也很简单,那就是用户通过docker client发送命令,docker daemon接收到命令后向docker registry发送image下载请求,下载后存放在本地,这样我们就可以使用image了。
- docker build
最后,让我们来看一下docker的底层实现。- docker基于Linux内核提供这样几项功能实现的:
- NameSpace
我们知道Linux中的PID、IPC、网络等资源是全局的,而NameSpace机制是一种资源隔离方案,在该机制下这些资源就不再是全局的了,而是属于某个特定的NameSpace,各个NameSpace下的资源互不干扰,这就使得每个NameSpace看上去就像一个独立的操作系统一样,但是只有NameSpace是不够。 - Control groups
虽然有了NameSpace技术可以实现资源隔离,但进程还是可以不受控的访问系统资源,比如CPU、内存、磁盘、网络等,为了控制容器中进程对资源的访问,Docker采用control groups技术(也就是cgroup),有了cgroup就可以控制容器中进程对系统资源的消耗了,比如你可以限制某个容器使用内存的上限、可以在哪些CPU上运行等等。 - 有了这两项技术,容器看起来就真的像是独立的操作系统了。
- NameSpace
- docker基于Linux内核提供这样几项功能实现的:
总结- docker是目前非常流行的技术,很多公司都在生产环境中使用,但是docker依赖的底层技术实际上很早就已经出现了,现在以docker的形式重新焕发活力,并且能很好的解决面临的问题
Docker 挂载
Docker 容器的默认存储是临时的,容器删除后数据会丢失。为了持久化数据,Docker 提供了 挂载 功能,将宿主机的目录或 Docker 管理的卷(Volume)与容器内的目录关联。
Docker 挂载的三种类型
| 类型 | 特点 |
|---|---|
| 绑定挂载(Bind Mount) | 直接将宿主机的目录或文件挂载到容器。适合开发调试,数据实时同步。 |
| 卷(Volume) | 由 Docker 管理的持久化存储,独立于容器生命周期,适合生产环境。 |
| tmpfs | 数据存储在内存中,容器停止后数据丢失,适合临时缓存。 |
| 挂载类型 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 绑定挂载 | 开发调试、实时同步 | 直接操作宿主机文件 | 路径依赖宿主机,不可移植 |
| 卷(Volume) | 生产环境、数据持久化 | Docker 管理,跨容器共享 | 需要额外管理卷 |
| tmpfs | 临时缓存、敏感数据 | 数据不落盘,安全性高 | 容器停止后数据丢失 |
挂载的核心参数
-v或--mount:用于指定挂载。- 绑定挂载:
1
docker run -v /host/path:/container/path ...
- 卷挂载:
1
docker run -v my-volume:/container/path ...
- 绑定挂载:
挂载的典型场景
| 场景 | 挂载类型 | 说明 |
|---|---|---|
| 数据持久化 | 卷 | MySQL 数据库、日志文件等。 |
| 配置文件管理 | 绑定挂载 | 实时更新 Nginx、MySQL 配置文件。 |
| 开发调试 | 绑定挂载 | 挂载代码目录,修改代码后容器内自动生效。 |
| 日志收集 | 绑定挂载 | 将日志目录挂载到宿主机,便于分析。 |
案例 1:Nginx 静态网页挂载
目标:将宿主机的网页文件挂载到 Nginx 容器中,实现动态更新。
步骤:
创建宿主机目录:
1
2mkdir -p /home/user/nginx/html
echo "Hello Docker!" > /home/user/nginx/html/index.html启动容器并挂载目录:
1
2
3
4
5docker run -d \
--name my-nginx \
-p 80:80 \
-v /home/user/nginx/html:/usr/share/nginx/html \
nginx:latest-v:将宿主机的/home/user/nginx/html挂载到容器的/usr/share/nginx/html(Nginx 默认网页目录)。
验证结果:
- 访问
http://localhost,应显示 “Hello Docker!”。 - 修改宿主机的
index.html内容,刷新网页即可看到更新。
- 访问
案例 2:MySQL 数据持久化
目标:将 MySQL 数据目录挂载到宿主机,确保容器删除后数据不丢失。
步骤:
创建宿主机目录:
1
mkdir -p /home/user/mysql/data
启动 MySQL 容器并挂载数据目录:
1
2
3
4
5
6docker run -d \
--name my-mysql \
-e MYSQL_ROOT_PASSWORD=your_password \
-p 3306:3306 \
-v /home/user/mysql/data:/var/lib/mysql \
mysql:8.0/var/lib/mysql是 MySQL 默认的数据存储目录。
验证数据持久化:
- 进入容器执行 SQL 操作:
1
2docker exec -it my-mysql mysql -u root -p
CREATE DATABASE test_db; - 删除容器后重新启动:
1
docker run -d ... # 使用相同挂载配置
- 新容器中的
test_db数据库仍存在。
- 进入容器执行 SQL 操作:
案例 3:使用 Docker 卷管理数据
目标:通过 Docker 管理的卷实现 MySQL 数据持久化。
步骤:
创建 Docker 卷:
1
docker volume create mysql-volume
启动 MySQL 容器并挂载卷:
1
2
3
4
5
6docker run -d \
--name my-mysql \
-e MYSQL_ROOT_PASSWORD=your_password \
-p 3306:3306 \
-v mysql-volume:/var/lib/mysql \
mysql:8.0查看卷信息:
1
docker volume inspect mysql-volume
常见问题与注意事项
权限问题
- 问题:挂载后容器内无法写入数据。
- 解决:
- 确保宿主机目录的权限允许容器用户访问(如
chmod 777 /host/path)。 - 使用
--user参数指定容器内运行用户。
- 确保宿主机目录的权限允许容器用户访问(如
挂载目录为空
- 问题:宿主机目录为空时,容器内的原有数据会被清空。
- 解决:
- 提前在宿主机目录中准备好需要的文件(如 MySQL 的配置文件)。
- 或使用卷挂载(Docker 会自动复制容器内的数据到卷中)。
挂载冲突
- 问题:挂载的目录与容器内已有文件冲突。
- 解决:确保挂载的目录结构与容器内一致(如 Nginx 的配置文件路径)。






