站内链接:

What?

介绍

本文章主要讲解 docker 的基本知识点:

  • 用以总结和理清整个 docker 环境建立的过程;
  • 用以加深 docker 的基本命令使用,
  • 用以平常使用 docker 时的快速查询操作

所以, 文档中会尽可能的加入基本命令的使用, 并且会持续性的更新该文档.

概念

Docker 项目的目标是实现轻量级的操作系统虚拟化解决方案, 让用户方便, 快速的操作一个轻量级的’虚拟机’. Docker 项目基于 Linux 容器(LXC)等技术, 进行一定的封装, 满足如下的简单需求:

  • 极小的资源浪费, 不想 VM 那样使用 hypervisor 来模拟硬件, 造成极大的资源消耗
  • 快速的启动时间, 便捷的服务
  • 快速的部署复制

docker 和传统的虚拟化不同在于: container 是在 OS 层面上进行虚拟化, 直接复用本地主机的 OS, 而传统的虚拟化则是使用 Hypervisor, 在硬件/系统层面上进行虚拟化. Container 的产生也是因为使用hypervisor仍旧无法满足资源的高效利用. 利用 container 技术在进程级别进行虚拟化操作, 以 MB/KB 级别启动一个实例, 实现资源的灵活高效使用.

  • 传统虚拟机: Server(物理服务器)–>Host os–>Hypervisor–>Guest OS–>Bins/Libs–>App
  • Docker: Server–>Host OS–>Docker Engine–>Bins/Libs–>App

优点

docker 相比传统的虚拟化方式, 具体更高的性价比:

  • 高效的资源利用: docker 中应用的执行速度, 资源消耗远远低于传统虚拟机, 能够在同样的设备上运行更多的应用
  • 快速的启动时间: 本质上 container 是一个进程, 所以一个 docker 应用的启动可以在秒级/毫秒级
  • 一致的环境: 确保一致性是团队开发中的重中之重,使用 docker 镜像确保除内核外的完整运行环境
  • 持续交付和部署: 利用镜像, 结合’持续集成’进行集成测试, 结合’持续部署’进行自动部署
  • 轻松的迁移: 环境的一致性, 在任何一个平台上都会正常的运行
  • 轻松的维护和扩展: 利用多层存储和镜像技术, 容易复用和维护.

Install

版本命名

2017-3-1 开始, docker 包的格式如下: YY.MM, 其中 Stable(社区)版本每一个季度发行, Edge(收费)版本每一个月发布一次.

Ubuntu

手动安装:

1
2
sudo apt-get update
sudo apt-get install docker.io

使用脚本自动安装, 类似 pip 的安装:

1
2
3
4
# 下载安装脚本
curl -fsSL get.docker.com -o get-docker.sh
# 使用阿里云的源服务, 也可以不指定源, 直接执行安装脚本
sudo sh get-docker.sh --mirror Aliyun

启动 docker:

1
2
3
4
5
6
# ubuntu16.04
sudo systemctl enable docker
sudo systemctl start docker

# ubuntu14.04
sudo service docker start

创建 docker 用户组, 确保 docker 能够使用 UNIX SOCKET 和 docker 引擎通讯:

1
2
sudo groupadd docker
sudo usermod -aG docker $USER

测试安装是否成功:

1
2
# 下载官方自有检查的镜像服务, 检查docker的安装是否正常, 这个命令适用于所有的平台
docker run hello-world

使用国内镜像, 以便加速镜像的下载速度(利用 docker 中国镜像), 对于 ubuntu14.04 系统, 编辑/etc/default/docker, 添加如下的配置:

1
DOCKER_OPTS="--registry-mirror=https://registry.docker-cn.com"

对于 Ubuntu16.04, Debian8+, centos7+, 编辑/etc/docker/daemon.json, 添加:

1
2
3
{
"registry-mirrors": ["https://registry.docker-cn.com"]
}

对于 window 10, 直接在 settings–>Daemon–>Registry mirros 中添加加速器地址. 对于 MacOS, 在 Preferences–>Daemon–>Registry mirros 中添加加速器地址. 注意, 任何一个系统中的 docker, 在更改配置后都需要两部操作: 重启 docker daemon 服务, 重启 docker 服务. 最后, 检查加速器配置是否生效, 并尝试登录 docker 服务, 以便更加方便的下载 docker 镜像:

1
2
3
4
# 查看
docker info
# 提前登陆docker, 更加方便的下载镜像
docker login

MacOS

1
brew cask install docker

其他配置部署, 参考ubuntu

Centos

手动安装, 其中脚本自动安装件ubuntu, 强烈推荐后者:

1
2
3
sudo yum install -y yum-utils \
device-mapper-persistent-data \
lvm2

其他配置见 ubuntu 章节介绍, 另外, 对于 centos 系统, 还需要额外的内核配置, 编辑/etc/sysctl.conf, 增加如下配置:

1
2
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1

之后, 重新加载配置即可: sudo sysctl -p

Window

下载, 同时必须开启 Hyper-V, 其他配置同ubuntu

Glossary

仓库

Repository–一个集中存储, 分发镜像的服务. 一个 Docker Registry 可以包含多个仓库(Repository), 一个仓库可以包含多个标签(tag), 一个标签表示一个镜像. 流入 ubuntu:16.04 就表示某一个公共镜像云上仓库为 ubuntu, tag 为 16.04 的镜像. 一般而言, 如果非官方镜像, 仓库名经常以jwilder/nginx-proxy格式出现, 其中前者为用户名, 后者为对应的软件名.

注意, 区分RepositoryRegistry的区别.

镜像

镜像类似开发中的类概念, 容器相当于实例概念, 基于一个镜像可以创建多个容器, 他们是互相依赖的, 一旦某个容器在运行中, 则依赖的镜像就无法删除. 注意, 镜像不包含任何动态信息, 构建完成之后就不会发生任何改变, 参考编程中的的定义, 当然 container 就是一个实例, 包含动态信息.

Docker 镜像(Image), 相当于一个root系统. 对于一个 OS 而言, 分为内核/用户空间, docker 就是提供一个新的文件系统, 让内核挂载并提供用户控件支持. 该文件系统, 提供了容器运行时所需的 app, library, configure 等, 还会包含一些基础的环境配置, 用户, 匿名卷等信息.

docker 基于 container 技术, 利用 Union FS 技术, 以分层存储的架构, 整个镜像是一个虚拟的概念, 每一次对镜像的增加都是原有镜像的基础之上, 累加一个新的存储层. 分层存储似的镜像的复用, 定制更加方便. 注意, 对镜像构建时, 最好每次构建结束前, 尽可能的删除无用的额外的信息, 避免镜像过大, 被他人重新构建时造成大量冗余数据的存在, 以尽可能小的存储空间提供属于这个镜像的功能.

容器

Container 的实质就是进程, 不同于宿主机的进程, container 进程拥有属于自己的命名空间, 拥有自己的root文件系统, 自己的网络配置, 自己的用户 ID. container 进程运行在一个隔离的环境中, 独立于宿主机的其他进程, 从而能够更加安全的运行. 容器中存储也是使用分层存储策略, 在每一个镜像启动的时候, 会在以镜像为基础的分层系统中创建一个可 R/W 的容器存储层, 该存储层的生命周期同 container 保持一致, 容器一旦消亡, 数据都会全部丢失.

数据卷

根据容器章节可知, 容器存储层中的数据会跟随容器的消亡而销毁, 这明显不适用于 App 运行过程中的日志记录, 数据持久化, 此时就需要 Volume 的帮助. 任何在 Volume 的写入操作都会直接跳过容器存储层, 直接对宿主机(网络存储)进行读写操作. 数据卷的声明周期独立于容器. 另见volume

Repository

基本介绍

Repository是集中存储镜像的地方, Registry注册服务器是管理仓库的具体服务器, 每一个服务器上都有很多的仓库, 类似 github, coding 和开源项目的关系.当然, 大部分其实两者是一致的, 只是不要混淆即可.

公有仓库

一般而言, 如果没有特殊需求, 可以采用公有仓库, 其中登录命令:

1
2
3
4
5
# 登录docker hub, 命令格式: docker login [options] [Server]
docker login
docker login --username bifeng --password kuang
# 登出: docker logout [SERVER]
docker logout

仓库相关命令:

1
2
3
4
5
6
7
# 搜索
docker search ubuntu
# 拉取镜像, 关于tag, 需要自己到registry官网查看
docker pull ubuntu:16.04

# 推送镜像到docker hub, 一般会加上自己的用户名
docker push ubuntu:16.05 username/ubuntu:16.05

私有仓库

公有仓库: 对所有人提供的仓库; 私有仓库: 使用docker-registry进行私有仓库的构建.
对于 TLS 的私有仓库, 还需要添加站点证书, 以确保能够成功完成镜像的上传, Ubuntu/Centos 上证书创建和添加:

1

Image

command

获取镜像, 命令格式docker pull [options] [docker registry address[:port]/]仓库名[:tag], 具体的命令说明可以通过docker pull --help来进行查看.

1
2
3
4
5
6
7
# 搜索
docker search ubuntu
# 获取
docker pull ubuntu
docker pull ubuntu:16.04
# 加载镜像, 对于已经发布的项目镜像包, 采用该方式自动加载多个镜像, tar文件中包含manifest.json分层架构框架信息, repositories包含镜像仓库信息.
docker load -i image.tar

列出镜像, 查看镜像的占用空间, 获取中间层镜像等. 注意, 下面命令中列出的镜像体积并非实际的镜像实际消耗,
因为 docker 镜像的多层存储结构, 并且可以继承, 复用, 不同的镜像可能会复用, 所以实际上可能占用空间更小:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 列出可用镜像
docker image ls
# 列出所有镜像, 包括虚悬镜像, 中间层镜像
docker image ls -a

# 列出所有dangling image(虚悬镜像), 中间层镜像则为除了虚悬镜像之外的无标签镜像
docker image ls -f dangling=true

# 添加过滤条件, 列出部分镜像
docker image ls ubuntu
docker image ls ubuntu:16.04
# 某一个tag之后的镜像
docker image ls -f since=ubuntu:14.04
docker image ls -f before=ubuntu:14.04
# 获取所有镜像的ID
docker image ls -q
# 使用GO语法, 指定格式打印
docker image ls --format "{{.ID}}: {{.Repository}}"

删除指定的镜像, 每一个镜像可能存在多个 tag, 如果想删除一个镜像, 需要先进行untagged操作, 伴随着 container
的检查, 如果发现有任何一个 tag 被当做 container, 则无法删除该镜像. 在取消所有标签之后, 最终执行delete操作.

1
2
3
4
5
6
7
8
# 删除本地镜像, 格式: docker image rm [option] <image1> [<image2> ...]
docker image rm ubuntu
# 使用ID进行删除工作
docker image rm 50

# 利用管道批量删除, 借助docker image ls -q
docker image rm $(docker image ls -q redis)
docker image rm $(docker image ls -q -f before=ubuntu:14.04)

commit

命令格式: docker commit [option] 容器 [镜像:版本], commit 命令一般用于特殊的场合:

  • 临时性的现场保存, 比如入侵后的保存现场, 将当前环境中的所有一切进行一个拷贝和备份, 制作成一个镜像.
  • 特定客户现场的小补丁, 仅仅适用于特定的一些场景, 一般不会进入该危险操作

注意commit不适合于镜像本身的定位(易扩展, 最小空间占用, 复用). commit 实际上将 container 的存储层也进行了保存, 即在原有的镜像的基础之上, 加上一个新的存储层, 但是实际上该存储层的冗余信息非常之多.docker commit意味着对镜像的操作都是黑箱操作, 俗称黑箱镜像, 除了制作者, 没有人知道这个镜像之中到底安装了什么?执行了什么命令? 在一段时间之后, 连制作者本人也不知道.

commit 的命令格式: docker commit [option] <容器名或者容器ID> [<仓库名>[:<标签>]], 可以加上一些冗余信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# a 保存某一个容器为镜像
docker commit --author 'bamboo' --message '被DDOS了先保存现场' ubuntu-test ubuntu:16.04.08
docker commit -a 'bamboo' -m '被DDOS了先保存现场' containerid ubuntu:commit-version-1

# 如果希望在commit的时候暂停容器, 则可以加入-p选项
docker commit --author 'bamboo' --message '被DDOS了先保存现场' -p ubuntu-test ubuntu:16.04.08

# b 查看镜像
docker image ls
# 查看该镜像中的存储层修改
docker diff ubuntu:16.04.08

# c 导出刚才导出的镜像
docker save ubuntu:16.04.08 -o ubuntu-commit.tar

# d 在其他地方导入
docker load -i ubuntu-commit.tar

dockerfile

如何定制一个镜像?我觉得条件应该如下:

  • 首先, 公开化, 让引用者能够清楚的了解这个镜像中都有什么? 都干了什么事情
  • 其次, 空间资源尽可能的小, 即确保镜像的空间占用小巧
  • 再次, 重复利用, 复用一直是软件工程持久的话题

使用 dockerfile 进行镜像的定制能够解决上述的需求, 一个 dockerfile 就是一条条的指令(instruction)构成, 每一条指令就相当于一层镜像, 用于描述如何构建了该层结构. 构建一个简单的 nginx 服务镜像, 首先编辑Dockerfile文件:

1
2
3
4
# 下载NGINX基础镜像
FROM nginx
# 更改文件
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html

之后, 使用build创建镜像, 格式: docker build [option] <上下文路径/URL/>:

1
docker build -t nginx:v4 -f Dockerfile .

请注意, dockerfile 和 docker 命令是两个完全不同的概念, 关于 dockerfile 的更多说明见站内笔记.

Container

容器进程

关于容器和虚拟机:

  • 容器是一个独立运行的一个或者一组应用, 以及与其关联的运行态环境;
  • 虚拟机则是模拟运行的一整套 OS 以及跑在上面的引用

容器的启动以及守护运行, 以及进行容器的运行环境, 当然这些都依赖于你首先有一个镜像.注意, 容器的启动就是一个进程, 如果进程运行结束, 那么容器也会停止.

基本命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 新建并且启动container, 这是一个瞬间执行完毕的容器
docker run ubuntu:16.04 /bin/echo 'hello world'
# 启动一个可交互的终端
docker run -t -i ubuntu:16.04 bash
# 启动一个后台运行的容器
docker run -t -i -d ubuntu:16.04 bash

# 终止某一个容器的运行
docker stop container-ID

# 启动
docker start container-ID
docker restart continer-ID

# 查看容器的运行日志
docker container logs container-ID
# 实时查看
docker container log -f [ID]

# 进入某一个正在运行中的容器, 一旦退出, 则相应的容器也会关闭, 相当于C中的exec
docker attach container-ID

# 作为一个子进程或者子线程进入容器
docker exec -it container-ID bash

注意-it-d的区别, 前者以独立的进程运行容器, 此时进入容器采用 ps 命令输出的进程只有shell, 在退出 shell 的时候容器会自动关闭, 后者则会以系统 shell 挂在后台运行. 关于容器的导入和导出, 见下方:

1
2
3
4
5
6
# 导出容器
docker export container-ID > ubuntu.tar.gz

# 导入容器
docker import ./ubuntu.tar.gz
cat ubuntu.tar.gz | docker import - test/ubuntu:v1.0

删除容器:

1
2
3
4
# 删除某一个容器
docker container rm ID1
# 删除所欲的终止状态的容器
docker container prune