0%

初识docker

初识docker

docker这么火,必须要了解一下了。

之前在别的部门接了个前端项目,做了个需求,是后台打印pdf文件,导出。找了很多方案,决定使用puppeteer无头chrome作为后端打印机用。本地开发没什么问题,但是到了线上问题就大了,在线编译无法通过,编译环境是centos环境,无法下载chrome,chrome的依赖也没有。做了很多额外的恶心的东西在里面。这时候,想到如果开发环境和部署环境一样的话,多好,完全没有这种问题。而docker就可以解决这些问题。

docker安装

由于国内网络的原因,docker-desktop 下载地址从阿里云开源镜像获取https://mirrors.aliyun.com/docker-ce/win/stable/Docker%20Desktop%20Installer.exe

奇怪的是直接从外部访问https://mirrors.aliyun.com/docker-ce/win看不到stable目录。。

还是由于国内网络原因,docker-hub上的镜像拉取也很慢,可以使用以下镜像进行加速

镜像加速器 镜像加速器地址 专属加速器? 其它加速?
Docker 中国官方镜像 https://registry.docker-cn.com Docker Hub
DaoCloud 镜像站 http://f1361db2.m.daocloud.io 可登录,系统分配 Docker Hub
Azure 中国镜像 https://dockerhub.azk8s.cn Docker Hub、GCR、Quay
科大镜像站 https://docker.mirrors.ustc.edu.cn Docker Hub、GCR、Quay
阿里云 https://.mirror.aliyuncs.com 需登录,系统分配 Docker Hub
七牛云 https://reg-mirror.qiniu.com Docker Hub、GCR、Quay
网易云 https://hub-mirror.c.163.com Docker Hub
腾讯云 https://mirror.ccs.tencentyun.com Docker Hub

配置方式:windows打开docker-desktop配置页,Docker Engine选项

1
2
3
4
5
6
7
8
{
"registry-mirrors": [
"https://xxxx.xxx.xxxx.com"
],
"insecure-registries": [],
"debug": true,
"experimental": false
}

linux则是打开(没有就创建)/etc/docker/daemon.json,编辑并重启systemctl restart docker

docker的使用

首先拉取一个镜像,如果拉取很慢或者失败,请按照上面的国内源配置。

1
docker pull hello-world

然后跑一下这个镜像:

1
2
3
4
5
6
7
docker run hello-world

# 成功显示

# Hello from Docker!
# This message shows that your installation appears to be working correctly.
# ...

以上是一个最简单的容器应用。试试运行一个大型容器看看,gitlab的容器应用,gitlab如果自己部署的话实在太麻烦了,它要求安装几个数据库,系统的依赖等一系列前置要求,然后还要编译,再运行,实属不便。但是有了docker就方便多了。

先拉取gitlab-ce(gitlab-ce是社区版的意思)的镜像。

1
docker pull gitlab/gitlab-ce[:TAG]  # TAG 如果不手动填写那么默认为last

慢慢等,大概有2G,国内镜像的话还是比较快的,然后直接运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
docker run \
# i:--interactive 打开交互式命令行
# t:--tty 分配一个Tty终端
# d:--detach 后台运行
-itd
# 端口映射 格式一般为 -p [宿主机端口]:[容器内部端口] 可以写多组。
# 要保证宿主机的端口没有被占用,否则启动失败。
-p 8443:443 -p 8880:80 -p 2222:22 \
# 容器名称,方便后续操作
--name gitlab \
# 这个表示映射目录,将容器内的目录挂载到外面(一些资源文件夹,比如配置,静态资源,日志)方便修改和查看。这里先不用。
# --volume /u1/gitlab/config:/etc/gitlab \
# --volume /u1/gitlab/logs:/var/log/gitlab \
# --volume /u1/gitlab/data:/var/opt/gitlab \
gitlab/gitlab-ce

访问localhost:8880就能看到gitlab,部署非常快捷,这就是docker要解决的痛点:由于运行环境差异导致的产品部署困难。

有了docker,运行环境的差异被抹平了。同样的一个镜像,无论再哪个物理机器上运行,环境都是一致的。

docker命令

docker命令主要分为对镜像的操作和对容器的操作,以下通过操作ubuntu,实现从拉取镜像到最后删除镜像的一整套操作。

先拉取一个镜像

1
2
# 拉取镜像的格式为 IMAGE_NAME:TAG,比如ubuntu就有很多个镜像比如:ubuntu:18.04
docker pull ubuntu:20.04

然后创建一个容器

1
2
3
# i:--interactive 打开交互式命令行
# t:--tty 分配一个Tty终端
docker create -it --name DOCKER_NAME ubuntu:20.04

接着运行

1
2
3
# i:--interactive 打开交互式命令行
# a:--attach 附着到Tty终端,不传这个参数,它就在后台运行。
docker start -ai CONTAINER_NAME

发现进入了ubuntu的root用户

1
2
docker start -ai ubuntu:20.04
root@e5cbbc1fe725:/#

嗯,现在可以运行linux命令了。

docker createdocker start 可以合并起来, 等于docker run

1
docker run -it --name=CONTAINER_NAME ubuntu:20.04

查看容器状态

1
2
3
docker ps -a
// 或者
docker container list -a

根据容器状态处理下一步,比如要停止容器后台运行

1
docker stop CONTAINER_NAME_OR_ID

停止后删除容器

1
docker rm CONTAINER_NAME_OR_ID

最后删除镜像

1
docker rmi IMAGE_NAME:TAG

主要就是两大类,操作容器和操作镜像,具体操作可以看Docker教程

如何理解容器

启动一个容器就是启动一个进程(进程内部新建的进程也可以认为是一个容器,只不过不可见)。

把docker run 一个容器理解成运行一条命令,镜像内部已经把很多命令封装好了,暴露在外部的参数,给用户自定义。

所以一条命令可以前端运行,也可以后台运行,docker run 附加参数就可以做到。

容器大概不能像普通操作系统一样使用,比如就算镜像集成了openssh,咱们docker run容器后台运行,是无法从外部ssh连接的,它运行的只是(拿ubuntu举例)/bin/bash这个入口命令,而openssh的服务进程并没有启动,只能进入容器手动启动openssh服务。也可以手动更改入口命令,让openssh随着入口命令一起启动,这样就能在docker run后直接通过ssh连接。

总而言之:一个容器就是一个进程。不能简单当作系统来理解,毕竟docker要的是轻量,如果做的和真实系统一样,一开始启动一大堆服务的话,轻量的特性就不存在了。

镜像的构建

docker的镜像构建方式主要有两种,容器构建,dockerfile构建

通过容器构建

创建容器后,在内部进行了很多运行环境的配置工作,配置完成后想要把这个容器作为镜像,方便后续使用。

镜像的制作很像git操作,修改了很多文件,git status就会显示改了哪里,docker也差不多,运行以下命令查看容器进行了哪些改动。

1
docker diff CONTAINER_NAME_OR_ID

和git一样,改好的代码需要git commit,docker也一样有commit命令

1
2
3
4
5
6
7
8
docker commit [选项] <容器ID或容器名> [<仓库名>[:<标签>]]

docker commit \
--author "YOUNAME <xxxxx@hikvision.com>" \
--message "修改很多东西" \
CONTAINER_NAME_OR_ID \
IMAGE_NAME:TAG
sha256:07e33465974800ce65751acc279adc6ed2dc5ed4e0838f8b86f0c87aa1795214

一个新的镜像就诞生了,如果觉得ok,直接上传到dockerhub,共享给别人使用。

通过dockerfile构建

上面的例子,已经可以制作镜像了,但是这里产生了一个问题。

容器配置了很多东西在里面,但是这个镜像基本不能一摸一样重复的制作出来,除非很简单。要是这个镜像丢了就完蛋了,因为整个构建过程不可见,不可重复。

所以有了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
31
32
33
34
35
FROM ubuntu:20.04 # 源镜像

MAINTAINER xxxx xxxx@hikvision.com.cn # 作者信息

COPY ./download/* /root/download/ # 复制文件到镜像

COPY ./scripts/* /root/scripts/

SHELL ["/bin/bash", "-c"] # 指定运行dockerfile RUN命令的shell

# 启动命令 docker run 的时候运行
CMD echo $HOME
# 等于
CMD [ "sh", "-c", "echo $HOME" ]

# 入口命令和CMD差不多,但是这个可以动态加参数
ENTRYPOINT [ "curl", "-s", "https://ip.cn" ] # docker run myip -i 就会带上 -i 参数

# 构建详情
RUN echo 'start build' && \
# 配置源
cat /root/download/sources.list > /etc/apt/sources.list && \
# 设置时区
ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
# 设置时区
echo 'Asia/Shanghai' > /etc/timezone && \
# 更新安装依赖
apt update && \
apt install -y git wget tree openssh-server && \
# 清理工作
apt remove wget -y && \
apt auto-remove -y && \
apt clean && \
rm -rf /var/lib/apt/lists/* && \
rm -rf /tmp/*

dockerfile 中很常见的几个命令FROMMAINTAINERCOPYSHELLRUN

前三个好理解,SHELL命令为啥指定shell,这里是有原因的。

dockerfile默认的SHELL是 [“/bin/sh”, “-c”],sh没有source命令,会导致后续无法应用.bashrc中配置的环境变量。

RUN命令就是构建脚本,可以写多个,上面每一步都可以拆成单个RUN命令,但是RUN命令每运行一次就会commit一次,运行越多,commit越多,最后导致镜像文件很大。

如果写到单个RUN命令里面,只commit一次,借助RUN尾部的清理命令,一次commit下来增加的体积就会小很多。

在dockerfile目录中运行 docker build -t IMAGE_NAME:TAG .后面那个点不能省略,表示构建目录为此目录。

新镜像诞生。

容器编排

在到处都是分布式系统的年代,一套系统拥有N个服务,也就是会有N个容器,那么如何管理容器就成了十分紧迫的需求,比如给容器分配cpu,内存,端口等等。所以需要一个容器资源管理系统来处理这些问题。

容器管理平台的基本特征:

  • 调度
  • 资源管理
  • 服务发现
  • 健康检查
  • 自动伸缩
  • 更新和升级

所以业界有了Kubernetes(k8s),Docker Swarm,Apache Mesos等容器编排平台,k8s显然成为了领导者,这里不深入了。

docker基本原理

vmware之类的虚拟机可以进行硬件级别的虚拟,很强大,但是太重量级了,资源消耗很大,吃cpu,吃内存,启动慢。

docker利用了一种新的思路,假设虚拟的系统和宿主系统用的一样的内核,只要共享这个系统内核就没有必要再模拟硬件的输入输出这种重量级的工作了。甚至可以在共享内核的基础上,创建各类进程,来虚拟完整系统功能。我们把这种操作系统级别的轻量化虚拟机,叫做容器

容器共享的是宿主机器的内核,要做到接近硬件虚拟机的效果,就要将文件系统,进程系统,内存系统,等等一系列东西和宿主机器进行隔离。最大限度的接近虚拟机。

这也解释了为什么windows版docker要依赖hyperv启动,原因就是上面说的容器需要共享宿主内核,而宿主平台是windows,不能给linux容器共享内核,所以docker启动必须依赖hyperv,docker启动时会在hyperv中启动一个真实的虚拟机,利用这个linux的虚拟机的内核,做共享内核,在这个基础上运行容器。

同时也解释了容器是进程这个概念,和我们理解的操作系统不一样。

docker分层文件系统

因为镜像包含操作系统完整的 root 文件系统,其体积往往是庞大的,因此在 Docker 设计时,将其设计为分层存储的架构。所以严格来说,镜像并非是像一个 ISO 那样的打包文件,镜像只是一个虚拟的概念,其实际体现并非由一个文件组成,而是由一组文件系统组成,或者说,由多层文件系统联合组成。

镜像构建时,会一层层构建,前一层是后一层的基础。每一层构建完就不会再发生改变,后一层上的任何改变只发生在自己这一层。比如,删除前一层文件的操作,实际不是真的删除前一层的文件,而是仅在当前层标记为该文件已删除。在最终容器运行的时候,虽然不会看到这个文件,但是实际上该文件会一直跟随镜像。因此,在构建镜像的时候,需要额外小心,每一层尽量只包含该层需要添加的东西,任何额外的东西应该在该层构建结束前清理掉。

分层文件系统示意图:

1
2
3
4
5
6
7
8
9
10
+----------------------------------+
| current layer | <- 当前层
|------------------------------+ |
| commit3 (read only) | |
|--------------------------+ | |
| commit2 (read only) | | |
|----------------------+ | | |
| commit1 (read only)| | | |
| | | | |
+----------------------------------+

镜像包含了四层文件系统,底下三层都是只读的,只有当前层可以读写。

可以发现分层文件系统有点类似git代码库,除非代码回退,否则之前构建的数据永远存在。docker构建层级越深镜像体积越大,git代码库也是一样,commit越多仓库体积越大,代码全删了也没用。

所以构建docker镜像层级不能太深,尽量选择原生的docker镜像做定制,一层搞定是最好的,否则docker镜像体积会明显增大。

总结

docker大大降低了运维的难度,创造性的容器应用充满了无限的想象力,未来大型应用的容器化是绝对的趋势,拥抱未来!