- Why Docker?
- Concept
- Installation
- Ubuntu
- Cheatsheet
- Usage
- docker run
- docker image
- docker search
- docker rm/rmi
- docker pull
- docker ps
- exit
- docker start/restart/stop/kill
- docker logs
- docker top
- docker inspect
- docker exec/attach
- docker cp
- docker build
- * Other command
- Docker Image Architecture
- Data Volume Containers
- docker run -v
- Volume From
- Dockerfile
- Usage
- Reference
- Example 1
- Example 2
- Example 3
- Troubleshotting
- RUN 命令没有显示输出
Docker: Getting Started
Why Docker?
- 开发团队与认为, 团队之间经常互相扯皮, 主要的原因就是因为环境和配置有一定的不同
- 比如公钥秘钥, 各类环境变量
- 对于多个集群, 运维需要重复安装很多个环境, 最麻烦的是有一些项目会用到不同版本的环境
- Docker 可以将一部分 代码/配置/系统/环境变量/数据 等等一系列东西全部包含进去.
- 这个地方的 Docker Image 甚至可以把软件也一起安装了
- 从此以后提交给运维的就是一个完全打包好的镜像
- 对比以前的虚拟技术
- 传统虚拟机 (e.g. Virtual Box)
- 虚拟一套硬件
- 其实在宿主机里面还模拟了虚拟机的内核和底层
- 启动慢, 占用高, 步骤多
- 虚拟一套硬件
- Docker
- 一次构建随处运行
- 用的是 Linux 容器虚拟化
- 更少的抽象层: 容器内没有自己的内核, 直接使用宿主机的内核
- 容器之间相互隔离
- 传统虚拟机 (e.g. Virtual Box)
Concept
dockerfile 源码 是原材料, image 是交付品, container 是运行示例
- Docker
- 是运行的载体以及管理引擎, 所有的操作都通过 Docker Daemon 处理
- Image
- 实际上就是一个 Template, 一般来说是只读的模板
- 打包好的环境一般就是一个镜像文件
- 通过这个镜像生成 docker 容器
- Container
- Image 实例化后的一个 Instance
- 单个 Container 可以单独启动/关闭/停止/删除
- 可以把容器看成一个简易版的 Linux 环境 运行在其中的应用程序
- Repository
- 实际上就是 Image 的内容
- DockerHub 是最大的公开仓库
- 国内可以使用阿里云和网易云的镜像
Installation
Install Docker Engine on Ubuntu | Docker Documentation
Ubuntu
代码语言:javascript复制基于 EC2, Ubuntu 20.04
sudo apt install docker.io
Cheatsheet
代码语言:javascript复制# 手动进入容器
sudo docker run -it [IMAGE] /bin/bash
# 一些情况下 bash 不够用, 改为 sh
sudo docker run -it [IMAGE] /bin/sh
Usage
docker --help 检查所有命令
docker run
代码语言:javascript复制docker run [OPTIONS] IMAGE [COMMAND] [ARG……]
docker run hello-world
# -i 交互模式运行, -t 分配一个伪输入终端, 一般一起使用
docker run -it centos
docker run -it centos --name mycentos
# 启动终端并执行自定义命令
docker run -it centos npm run dev
# 后台运行, 不弹出交互窗口也不切换
# 后台运行必须有一个持续的进程, 不然就会自动退出
docker run -d
# 打开一个新终端, 运行 tomcat
# -p 将 8080 映射到 8080
# [外部 DOCKER PORT]:[内部 IMAGE PORT]
docker run -it -p 8080:8080 tomcat
# -P 随机分配端口, 启动后可能直接看不到端口, 需要 docker ps 来查看
docker run -P tomcat
docker ps
# 携带环境变量
docker run --env VAR1=value1 --env VAR2=value2 ubuntu
默认会下载 latest 版本, 可以带上特定的 tag: docker run hello-world: latest
- 则检查本地是否存在这个
hello-world
image, 如果有, 实例化产生 container 并运行 - 如果还没有, 就会去默认 registry 拉取下来, 如果可以找到, 那么拉下来, 实例化产生 container 并运行
docker image
代码语言:javascript复制# 列出本地所有镜像
docker images
# 列出本地所有镜像 包括中间层
docker images -a
# 列出本地所有镜像 ID
docker images -aq
docker search
代码语言:javascript复制# 到 dockerhub 搜索镜像关键字
docker search tomcat
docker rm/rmi
代码语言:javascript复制# 删除一个本地的镜像, 但是如果有容器正在使用, 将不会删除
docker rmi hello-world
# 删除多个镜像
docker rmi a:latest b:latest c d e f
docker rmi -f ${docker ps -a -q}
# 删除容器, 注意不带 i 就是删除容器, 带了 i 就是删除镜像
docker rm
# 删除多个容器
docker rm -f ${docker ps -a -q}
docker ps -a -q | xargs docker rm
docker pull
代码语言:javascript复制# 下载一个 image
docker pull centos
# 检查已经下载的 images
docker images
docker ps
代码语言:javascript复制# 列出当前 docker 所有正在运行的 container, 注意不是 image
docker ps
# 列初当前正在运行的, 以及以前历史运行过的. 这个地方可以通过状态来识别是否已经在运行.
docker ps -a
# 列出上一次运行的 container
docker ps -l
# 列出最近 50 个 container
docker ps -n 50
exit
代码语言:javascript复制# 退出容器, 终止容器并退出.
exit
# 不中止容器并退出
(Ctrl P Q)
docker start/restart/stop/kill
代码语言:javascript复制docker start [CONTAINER/NAME]
docker restart [CONTAINER/NAME]
# 自然关闭
docker stop [CONTAINER/NAME]
# 强制停止
docker kill [CONTAINER/NAME]
docker logs
代码语言:javascript复制# 查看日志, -t 包含时间戳, -f 跟随显示, --tail 默认尾部全部
docker logs -f -t --tail [CONTAINER]
docker logs -f -t --tail 3 [CONTAINER]
docker top
代码语言:javascript复制# 查看容器内运行的进程
docker top [CONTAINER]
docker inspect
代码语言:javascript复制# 查看容器内运行的细节
docker inspect[CONTAINER]
docker exec/attach
代码语言:javascript复制# 进入容器
docker attach [CONTAINER]
# 不进入容器就在容器中执行 ls -l /tmp, 并将结果返回宿主机
docker exec [CONTAINER] ls -l /tmp
docker cp
代码语言:javascript复制# 将容器内的 /tmp/test.log 拷贝到宿主机的 /root 文件夹
docker cp [CONTAINER]: /tmp/test.log /root
docker build
代码语言:javascript复制# 注意结尾有一个点符号
docker build -t [IMAGE_NAME]:TAG .
# -f [DOCKER_FILE] 如果不添加的话就会自动寻找文件名为 DockerFile 的文件
docker build -f [DOCKER_FILE] -t [IMAGE_NAME]:TAG .
* Other command
代码语言:javascript复制# 提交 container 副本令其成为一个新的 image
docker commit
# 列出镜像历史
docker history
Docker Image Architecture
镜像是一个 UnionFS (联合文件系统): 实际上是一种分层的, 高性能的, 轻量级的文件系统, 它支持对文件系统的修改作为一次提交来一层层叠加
一个 Image 可能引用了多个其他的镜像, 并且引用的镜像可以被多个 Image 引用 (类似 npm -g
的 模式)
- 最底层是 bootfs (boot file system): 主要包含 bootloader 和 kernel
- 上一层是 rootfs (root file system): 在 bootfs 之上, 包含的就是典型 Linux 系统中的 /dev, /proc, bin, /etc 等标准目录和文件, rootfs 就是不同操作系统的发行版
- rootfs 相比其他系统的发布包来说会小很多, 因为只需要包含最基本的命令工具以及程序库就可以. 毕竟底层直接使用 host 的内核.
Data Volume Containers
一般来说, 容器里面产生的内容和数据在容器关闭之后会直接消失. 然后就需要将一些数据保存出来做持久化.
可以使用的方式:
- 直接命令添加
- dockerfile
docker run -v
代码语言:javascript复制# 如果对应的 path 没有会自动生成, 可以让 container 内部的一个 path 和外部 host 的一个 path 建立 binding
docker run -it -v [HOST_PATH]:[CONTAINER_PATH] [IMAGE]
# 添加多个 binding
docker run -it -v [HOST_PATH]:[CONTAINER_PATH] -v [HOST_PATH]:[CONTAINER_PATH] [IMAGE]
# 建立 binding 之后使用 inspect 可以从 HostConfig.Binds 里面找到
docker inspect [CONTAINER]
# 限定权限的模式, :ro 代表 read only
docker run -it -v [HOST_PATH]:[CONTAINER_PATH]:ro [IMAGE]
Volume From
代码语言:javascript复制# 首先创建一个容器
docker run -it --name CONTAINER_1 [IMAGE]
# 然后在容器里面创建一些文件
# 根据相同的 Image 但是根据 CONTAINER_1 进行扩展
docker run -it --volumes-from CONTAINER_1 --name CONTAINER_2 [IMAGE]
随后两个 CONTAINER 里面会有相同的文件, 在其中任何一个 CONTAINER 里面修改会影响另一个
数据卷的生命周期持续到所有 CONTAINER 的引用消失为止
比如: 此时创建 CONTAINER_3, 然后做一些修改, 然后删除 CONTAINER_1 和 CONTAINER_2, CONTAINER_3 里面依然可以看到修改
Dockerfile
Usage
使用 dockerfile 的主要的步骤:
- dockerfile: 编写一个 dockerfile
- docker build: 构建的时候引用这个 dockerfile, 生成 image
- docker run: image 随后可以构建 container
可以看见在多数情况下,只有构建的时候会使用到这个 dockerfile
特性:
- 指令结构: 一段大写的单词后面带上一堆参数
- 从上到下执行
- 每一条指令都会新建一个镜像层, 并且对镜像进行提交
- 执行完毕后会提交一个新的镜像层, 并且给予刚提交的镜像运行一个新的容器
Reference
- FROM
- 设置一个初始的镜像, 在这个镜像上扩展
scratch
代表最原始的镜像
- WORKDIR
- 设置在镜像里面的工作目录
- ADD/COPY
- 将宿主机目录下的文件拷贝进镜像
- ADD 命令 比 COPY 多一个步骤, 会自动处理 URL 和解压 tar 压缩包
- VOLUME
- 用于数据保存和持久化
- 会在容器里面创建新的
- CMD
- 格式
- shell 格式:
CMD <Command>
- exec 格式:
CMD ["executable file", "arg1", "arg2", ……]
- shell 格式:
- Dockerfile 中多个 CMD 只会执行最后一个
- 一般会在 dockerfile 末尾加一段 CMD 命令, 使得不带参数跑
docker run
的时候运行这段 CMD 命令 docker run [IMAGE]
就会默认执行最后一段 CMDdocker run [IMAGE] npm run dev
忽略原本 docker file 中的 CMD 并执行 npm run dev
- 一般会在 dockerfile 末尾加一段 CMD 命令, 使得不带参数跑
- 格式
- ENTRYPOINT
- 和 CMD 类似, 区别在于这个是一定执行不会有替换最后一段 CMD 的情况, 并且会追加组合对应的命令
- ENTRYPOINT 的好处:
- 如果在 dockerfile 中设定了
CMD ["npm", "run","dev"]
随后想要在 docker run 的时候就无法给最后一段 CMD 添加额外参数- 即
CMD ["npm", "start"]
docker run [IMAGE] -i
≠CMD ["npm", "-i","start"]
- 这个时候必须使用 docker run 执行完整的命令或者重新 build container
- 即
- 但是 ENTRYPOINT 可以实现
- 即
ENTRYPOINT ["npm", "start"]
docker run [IMAGE] -i
=ENTRYPOINT ["npm", "-i","start"]
- 即
- 如果在 dockerfile 中设定了
- ONBUILD
- 当 build 一个继承镜像的时候触发, 父镜像在被子镜像继承后触发父镜像的 onbuild
- ENV
- 环境变量
Example 1
一个简单的 dockerfile:
代码语言:javascript复制FROM centos
VOLUME ["/folder1","/folder2"]
CMD echo "Done"
CMD /bin/bash
然后 build 一个新的 image
代码语言:javascript复制# 注意尾部有一个点用于当前目录
docker build -f [DOCKERFILE] -t [IMAGE_NAME] .
# 然后 docker run 跑起一个新的 container
# 新的 container 里面就会带上 folder1 和 folder2 两个目录
docker run -it [IMAGE]
# 需要注意的是, 就算这里没有使用 -v 来 bind host 的目录, docker 依然会生成一个目录用于数据持久化, 这个自动生成的逻辑只有通过 dockerfile 才会执行
Example 2
代码语言:javascript复制FROM centos
ENV mypath /tmp # 设置一个环境变量
WORKDIR $mypath # 设置容器内的工作目录, 设定到 /tmp
RUN yum -y install vim
RUN yum -y install net-tools
EXPOSE 80
CMD /bin/bash
Example 3
dockerFile1
FROM centos
ONBUILD RUN echo "this is father image" # 父镜像的 ONBUILD
假设使用上方 dockerfile 构建了一个镜像 centos1
: docker build -f dockerfile1 -t centos1 .
然后编写一个新的 dockerFile:
dockerFile2
FROM centos1 # 继承刚才创建的镜像
创建一个新的镜像: docker build -f dockerfile2 -t centos2 .
这个时候就会触发父镜像里面的 ONBUILD
Troubleshotting
RUN 命令没有显示输出
加上一段参数即可
代码语言:javascript复制docker build -t hello-world ./ **--progress=plain --no-cache**