1. 为什么要学习docker
- 节省配置环境的时间。
- 应用隔离,避免不同应用的依赖冲突,且应用CPU占用过高也无法超出设定值,不会影响其它应用。
- 开发、测试、生产环境得到统一,不会出现代码在测试环境正常,线上爆炸的尴尬场景。使得整个交付流程可控可信。
- 低成本迁移,在新服务器上拉取制作好的镜像并运行。
- 更易于持续交付和部署(CI/CD),配合 GitLab CI 的方式让部署流程更加的自动化和高效。
2. 底层原理
docker 底层使用了一些 linux 内核的特性,比较重要的有三个 namespace,cgroups 和 UnionFS。
2.1 namespace
docker 使用 linux namespace 构建隔离的环境,它由以下 namespace 组成
- pid: 隔离进程
- net: 隔离网络
- ipc: 隔离 IPC
- mnt: 隔离文件系统挂载
- uts: 隔离hostname
- user: 隔离uid/gid
2.2 control groups
也叫 cgroups,限制资源配额,比如某个容器只能使用 100M 内存
2.3 Union file systems
UnionFS 是一种分层、轻量级并且高性能的文件系统,支持对文件系统的修改作为一次提交来一层层的叠加。docker 的镜像与容器就是分层存储,可用的存储引擎有 aufs,overlay 等。 关于分层存储的详细内容可以查看官方文档 docker: About storage drivers
3. 基础概念
- 镜像:一个用来创建容器的模板 ,提供容器运行时所需的程序、库、资源、配置等文件,以及运行时准备的配置参数。(一个只读模板,包含创建Docker容器的说明。)
- 容器:镜像创建的运行实例。(镜像 + 可写层)
流程:
- 构建或拉取镜像
- 由镜像运行一个容器实例,实例内有我们的应用,且在指定端口提供服务
可读、可写层都是基于 unionFS ( linux 上使用的文件系统服务)
4. 镜像
4.1 获取镜像的途径
docker pull
拉取 Docker Hub (官方镜像仓库)的镜像。sh# docker pull 镜像名字:版本号/平台标志 # alpine的体积比较小 docker pull node:alpine
docker build
通过 Dockerfile(镜像配置文件)构建。sh# --no-cache=true:不使用缓存 # -t: 指定镜像名称和版本号/平台标志 node-base:10: 镜像明细 版本号/平台标志 # .: 指当前路径,利用当前目录的Dockerfile配置构建镜像 $ docker build -t demo:10 .
4.2 docker images 获取镜像列表
docker images
# REPOSITORY TAG IMAGE ID CREATED SIZE
# nginx alpine e46bcc697531 3 days ago 23.5MB
4.3 docker rmi 移除镜像
docker rmi nginx:alpine
4.4 docker push 将镜像推送到远程仓库
docker push nginx:alpine
4.5 docker inspect 查看镜像、容器信息
# 查看镜像信息
docker inspect nginx:alpine
# 查看容器信息
# -f 输出指定参数
# 此处是输出容器的IP地址
docker inspect -f {{.NetworkSettings.IPAddress}} container_name
5. Dockerfile 镜像配置文件
可以为自己的应用编写配置,用此配置来构造镜像。
Dockerfile一般放在前端目录下,这样可以理解成 代码 和 Docker镜像配置文件 打包在一起,这个包在任何服务器都是同样的执行效果。
5.1 FROM 基于基础镜像
基于一个某个基础镜像
FROM node:alpine
5.2 ADD 宿主机往镜像添加文件
将宿主机的文件或目录 添加到 镜像的文件系统中。
支持URL自动下载,自动解压。而 COPY 不会。
ADD 时会忽略 .dockerignore 配置里的文件夹/文件,该配置作用和.gitignore一样
ADD . /code
5.3 RUN 镜像中执行命令
RUN 在 构造镜像时 运行的。
在镜像中执行命令,由于 ufs 的文件系统,它会在当前镜像的顶层新增一层 多行运行时与Shell不同,Shell在同一进程中运行,前后之间有关系
RUN 每次都是单独的,运行在新增的层,前后两句 RUN 之间没有关系。
RUN cd /code
RUN echo "1" > world.txt
# 本质上
# 1. 执行了 cd /code 目前pwd: ~/code
# 2. 执行了 echo 1 > 1.txt 目前pwd: ~/
# 前后没有联系,新增的文件: ~/1.txt
5.4 WORKDIR 指定工作目录
指定工作目录 改变了后续各层的工作目录,对后续 RUN/CMD
有影响。
如果指定目录不存在,会新增目录。
WORKDIR /code/
RUN echo "1" > world.txt
# 本质上
# 1. WORKDIR /code/ 目前pwd: ~/code
# 2. 执行了 echo 1 > 1.txt 目前pwd: ~/code
# 新增的文件: ~/code/1.txt
5.5 CMD 指定容器如何启动
CMD 在 容器启动时 运行的。
指定容器如何启动,一个 Dockerfile 中只允许有一个 CMD。
CMD npm start
5.6 Dockerfile 文件
# 基于基础镜像
FROM node:alpine
# 将宿主机的文件或目录 添加到 镜像的文件系统中
# ADD src dest
ADD package.json package-lock.json /code/
# 指定工作目录 改变了后续各层的工作目录
# 对 RUN 有影响。因为 RUN 每次都是单独的,运行在新增的层上,前后两句 RUN 之间没有关系
WORKDIR /code/
# ADD 时会忽略 .dockerignore 配置里的文件夹/文件,该配置作用和.gitignore一样
ADD . /code/
# 在镜像中执行指令
# 多行运行时与Shell不同,Shell在同一进程中运行,前后之间有关系
# RUN 命令每次都是单独的,前后之间没有关系
RUN npm run build
# 指定容器如何启动
CMD npm start
6. 容器
容器 和 镜像的关系,像代码中 类 和 实例 的关系。 镜像是静态的定义,容器是运行的实例。容器可以被创建、删除、启动、暂停等。
6.1 常用操作
docker ps: 列出容器,默认列出正在运行的容器,-a 可以列出所有状态的容器。
docker create:创建容器
docker create
命令新建的容器处于停止状态,可以使用docker start
命令来启动它。sh# --name: 指定容器名称 docker create --name demo nginx:alpine
docker start:启动容器
如果镜像不存在,会尝试从默认的镜像仓库中下载。docker stop:暂停容器
docker rm:删除容器
docker run:创建一个容器并启动(create 和 start的结合)
如果镜像不存在,会尝试从默认的镜像仓库中下载。sh# --rm: 停止时删除容器 # -it: 可进行交互 # --name: 指定容器名称 # -p: 端口映射,对外提供服务 宿主机端口:容器端口 docker run --rm -it --name demo -p 8888:80 nginx:alpine
可以使用 curl localhost:8888
测试服务是否正常
- docker logs:查看容器日志
# -f:跟随输出,即进入日志文件一直显示最新的,不会打印后立马退出
# -t:显示时间
# -n x:显示最后x行
docker logs container_name -f -t
6.2 docker exec 进入容器后执行命令
进入容器,可以排查问题或查找相关信息,操作的灵活度大大增加。
# docker exec -it 容器名称 执行的命令
docker exec -it demo sh
6.3 docker port 列出容器的端口映射
docker port demo
# 80/tcp -> 0.0.0.0:8888
6.4 docker stats 查看容器资源占用
docker port stats
# CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
# f1fa5900f5e6 demo 0.00% 5.086MiB / 6.067GiB 0.08% 1.85kB / 1.23kB 0B / 0B 7
7. Docker Compose
Dockerfile 是一个镜像配置文件,用来构造镜像。
而根据镜像创造并启动容器,仍然需要敲入复杂的指令,例如
# --rm: 停止时删除容器
# -it: 可进行交互
# --name: 指定容器名称
# -p: 端口映射,对外提供服务 宿主机端口:容器端口
docker run --rm -it --name demo -p 8888:80 nginx:alpine
Docker Compose 可以将启动容器的参数变成配置文件 docker-compose.yaml
,也可以实现多个容器相互配合。
7.1 docker compose up
构建/拉取镜像并启动容器。
配置yaml文件,可以基于已有镜像。
# docker-compose.yaml
version: "3"
services:
app:
image: "nginx:alpine"
ports:
- 8000:80
container_name: "blog" # 指定容器名称
restart: always # 容器出错了 无限重启
也可以基于Dockerfile去构建镜像
# docker-compose.yaml
version: "3"
services:
app:
build:
context: . # Dockerfile 文件所在目录
dockerfile: Dockerfile # Dockerfile 文件名称
ports:
- 8000:80
container_name: "blog" # 指定容器名称
restart: always # 容器出错了 无限重启
总结
疑问
[x] 通过
docker pull
拉取下来的,CREATED 是指Docker Hub上该镜像的创建时间吗?
是的,作者的创建时间,可能时间对不上是操作系统时间不同。[x] “CMD不一定会跑”。 我感觉CMD在容器启动时是一定会运行,只是视频最后的操作是在启动之后进入容器,所以没看到命令。 这个理解对吗?
不对,CMD 命令不一定会被执行,因为他只是一个默认的启动命令,很容易被覆盖,例如docker run -it nginx:alpine sh
,CMD 命令就被sh
覆盖了。所以更容易理解 CMD 是在运行时期跑的一个命令、[x] UFS 的分层存储不理解
About storage drivers
由基础镜像作为元数据层,添加和移除文件都会生成新的层,每个层都只是与之前的层的有一小部分差异。
这些层堆叠在一起形成一个基础层“镜像”,这个镜像为可读层。创建新容器时,可以在基础层的顶部添加新的可写层。该层通常称为“容器层”。
提问
[x] 启动docker后如何进入容器?
docker exec -it container_name sh
[x] 如何查看容器的日志?
docker logs container_name -f -t
[x] docker的底层原理
name space 隔离环境
cgroups 限制资源配额