Docker 技术入门与实践

docker是dotcloud公司创始人 Solomon Hykes 在法国期间发起的一个公司内部项目,它是基于 dotCloud 公司多年云服务技术的一次革新,并于 2013 年 3 月以 Apache 2.0 授权协议开源,主要项目代码在 GitHub 上进行维护。

甚至由于 Docker 项目的火爆,在 2013 年底,dotCloud 公司决定改名为 Docker。

Docker 使用 Google 公司推出的 Go 语言 进行开发实现,基于 Linux 内核的 cgroup,namespace,以及 AUFS 类的 Union FS 等技术,对进程进行封装隔离,属于 操作系统层面的虚拟化技术。由于隔离的进程独立于宿主和其它的隔离的进程,因此也称其为容器。最初实现是基于 LXC,从 0.7 版本以后开始去除 LXC,转而使用自行开发的 libcontainer,从 1.11 开始,则进一步演进为使用 runC 和 containerd。

Docker 在容器的基础上,进行了进一步的封装,从文件系统、网络互联到进程隔离等等,极大的简化了容器的创建和维护,使得 Docker 技术比虚拟机技术更为轻便、快捷。

下面的图片比较了 Docker 和传统虚拟化方式的不同之处。

  • 传统虚拟机技术是虚拟出一套硬件后,在其上运行一个完整操作系统,在该系统上再运行所需应用进程;
  • 容器内的应用进程直接运行于宿主的内核,容器内没有自己的内核,而且也没有进行硬件虚拟,因此容器要比传统虚拟机更为轻便;

Docker VS 传统虚拟化:

1.1 docker底层技术

  • Namespaces
    • The pid namespace: Process isolation (PID: Process ID).
    • The net namespace: Managing network interfaces (NET: Networking).
    • The ipc namespace: Managing access to IPC resources (IPC: InterProcess Communication).
    • The mnt namespace: Managing filesystem mount points (MNT: Mount).
    • The uts namespace: Isolating kernel and version identifiers. (UTS: Unix Timesharing System).
  • Control groups
    • A cgroup limits an application to a specific set of resources.
  • Union file systems
    • File systems that operate by creating layers, making them very lightweight and fast.
  • Container format
    • The default container format is libcontainer.
    • In the future, Docker may support other container formats by integrating with technologies such as BSD Jails or Solaris Zones.

1.2 docker的优势

作为一种新兴的虚拟化方式,Docker 跟传统的虚拟化方式相比具有众多的优势。

  • 更高效的利用系统资源
    • 由于容器不需要进行硬件虚拟以及运行完整操作系统等额外开销,Docker 对系统资源的利用率更高。无论是应用执行速度、内存损耗或者文件存储速度,都要比传统虚拟机技术更高效。因此,相比虚拟机技术,一个相同配置的主机,往往可以运行更多数量的应用。
  • 更快速的启动时间
    • 传统的虚拟机技术启动应用服务往往需要数分钟,而 Docker 容器应用,由于直接运行于宿主内核,无需启动完整的操作系统,因此可以做到秒级、甚至毫秒级的启动时间。大大的节约了开发、测试、部署的时间。
  • 一致的运行环境
    • 开发过程中一个常见的问题是环境一致性问题。由于开发环境、测试环境、生产环境不一致,导致有些 bug 并未在开发过程中被发现。而 Docker 的镜像提供了除内核外完整的运行时环境,确保了应用运行环境一致性,从而不会再出现 「这段代码在我机器上没问题啊」 这类问题。
  • 持续交付和部署
    • 对开发和运维人员来说,最希望的就是一次创建或配置,可以在任意地方正常运行。

使用 Docker 可以通过定制应用镜像来实现持续集成、持续交付、部署。开发人员可以通过 Dockerfile 来进行镜像构建,并结合 持续集成(Continuous Integration) 系统进行集成测试,而运维人员则可以直接在生产环境中快速部署该镜像,甚至结合 持续部署(Continuous Delivery/Deployment) 系统进行自动部署。

  • 使用 Dockerfile 使镜像构建透明化,不仅仅开发团队可以理解应用运行环境,也方便运维团队理解应用运行所需条件,帮助更好的生产环境中部署该镜像。
  • 更轻松的迁移
    • 由于 Docker 确保了执行环境的一致性,使得应用的迁移更加容易。Docker 可以在很多平台上运行,无论是物理机、虚拟机、公有云、私有云,甚至是笔记本,其运行结果是一致的。因此用户可以很轻易的将在一个平台上运行的应用,迁移到另一个平台上,而不用担心运行环境的变化导致应用无法正常运行的情况。
  • 更轻松的维护和扩展
    • Docker 使用的分层存储以及镜像的技术,使得应用重复部分的复用更为容易,也使得应用的维护更新更加简单,基于基础镜像进一步扩展镜像也变得非常简单。此外,Docker 团队同各个开源项目团队一起维护了一大批高质量的 官方镜像,既可以直接在生产环境使用,又可以作为基础进一步定制,大大的降低了应用服务的镜像制作成本。

对比传统虚拟机优势:

特性 容器 虚拟机
启动 秒级 分钟级
硬盘使用 一般MB 一般GB
性能 接近原生 弱于
系统支持量 单机支持上千个容器 一般几十个

1.3 基本概念

Docker 包括三个基本概念:

  • 镜像(Image)
  • 容器(Container)
  • 仓库(Repository)

理解了这三个概念,就理解了 Docker 的整个生命周期。

1.3.1 镜像

Docker 镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。

Docker 设计时,就充分利用 Union FS 的技术,将其设计为分层存储的架构。所以严格来说,镜像并非是像一个 ISO 那样的打包文件,镜像只是一个虚拟的概念,其实际体现并非由一个文件组成,而是由一组文件系统组成,或者说,由多层文件系统联合组成。

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

1.3.2 容器

容器的实质是进程,但与直接在宿主执行的进程不同,容器进程运行于属于自己的独立的 命名空间。因此容器可以拥有自己的 root 文件系统、自己的网络配置、自己的进程空间,甚至自己的用户 ID 空间。容器内的进程是运行在一个隔离的环境里,使用起来,就好像是在一个独立于宿主的系统下操作一样。这种特性使得容器封装的应用比直接在宿主运行更加安全。

容器存储层的生存周期和容器一样,容器消亡时,容器存储层也随之消亡。因此,任何保存于容器存储层的信息都会随容器删除而丢失。 按照 Docker 最佳实践的要求,容器不应该向其存储层内写入任何数据,容器存储层要保持无状态化。所有的文件写入操作,都应该使用 数据卷(Volume)、或者绑定宿主目录,在这些位置的读写会跳过容器存储层,直接对宿主(或网络存储)发生读写,其性能和稳定性更高。 数据卷的生存周期独立于容器,容器消亡,数据卷不会消亡。因此,使用数据卷后,容器删除或者重新运行之后,数据却不会丢失。

1.3.3 仓库

镜像构建完成后,可以很容易的在当前宿主机上运行,但是,如果需要在其它服务器上使用这个镜像,我们就需要一个集中的存储、分发镜像的服务,Docker Registry 就是这样的服务。

一个 Docker Registry 中可以包含多个 仓库(Repository);每个仓库可以包含多个 标签(Tag);每个标签对应一个镜像。 通常,一个仓库会包含同一个软件不同版本的镜像,而标签就常用于对应该软件的各个版本。我们可以通过 <仓库名>:<标签> 的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签,将以 latest 作为默认标签。

以 Ubuntu 镜像 为例,ubuntu 是仓库的名字,其内包含有不同的版本标签,如,16.04, 18.04。我们可以通过 ubuntu:16.04,或者 ubuntu:18.04 来具体指定所需哪个版本的镜像。如果忽略了标签,比如 ubuntu,那将视为 ubuntu:latest。

私有 Docker Registry:

开源的 Docker Registry 镜像只提供了 Docker Registry API 的服务端实现,足以支持 docker 命令,不影响使用。但不包含图形界面,以及镜像维护、用户管理、访问控制等高级功能。在官方的商业化版本 Docker Trusted Registry 中,提供了这些高级功能。 除了官方的 Docker Registry 外,还有第三方软件实现了 Docker Registry API,甚至提供了用户界面以及一些高级功能。比如,Harbor 和 Sonatype Nexus。

  • Docker 分为 CE1) 和 EE2) 两大版本。
  • Docker CE 分为 stable test 和 nightly 三个更新频道。每六个月发布一个 stable 版本 (18.09, 19.03, 19.09…)。

Warning:

  • 官文建议使用overlay2,要求内核4.0+,而RHEL/CENTOS 7 只要3.10.0-514+ 就可以了,(初始化配置就直接yum update到最新了,这个issue基本就忽略了)
  • 另外原来早期centos7上使用docker会有xfs的dtype=0的issue,但是新版本的centos7都解决了,所以centos7上使用docker是没啥问题了。dtype=1,可使用docker info命令检查“ftype=1”来确认。

2.1 CentOS 7

卸载旧版本: 3) 4)

yum remove docker \
                  docker-client \
                  docker-client-latest \
                  docker-common \
                  docker-latest \
                  docker-latest-logrotate \
                  docker-logrotate \
                  docker-engine

安装新版本:

yum install -y yum-utils device-mapper-persistent-data lvm2
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
sed -i 's%download.docker.com%mirrors.ustc.edu.cn/docker-ce%g' /etc/yum.repos.d/docker-ce.repo
yum install docker-ce docker-ce-cli containerd.io

2.2 debian 10

卸载旧版本:

apt-get remove docker docker-engine docker.io

安装新版本:

apt install gnupg -y
curl -fsSL https://mirrors.ustc.edu.cn/docker-ce/linux/debian/gpg | sudo apt-key add -
cat >> /etc/apt/sources.list <<'EOF'
deb https://mirrors.ustc.edu.cn/docker-ce/linux/debian buster stable
EOF
apt update -y
apt install docker-ce -y

或者使用脚本自动安装:5)

curl -fsSL get.docker.com -o get-docker.sh
bash get-docker.sh --mirror Aliyun
# bash get-docker.sh --mirror AzureChinaCloud

2.3 启动 docker-ce

systemctl enable docker
systemctl start docker
systemctl status docker
# 测试
docker info
docker run --rm hello-world

2.4 其他

列出某个image的全部tags6):

cat > /usr/bin/docker-tags<<'EOF'
#!/bin/sh
# Usage: docker-tags ubuntu centos
for Repo in $* ; do
  curl -s -S "https://registry.hub.docker.com/v2/repositories/library/$Repo/tags/" | \
    sed -e 's/,/,\n/g' -e 's/\[/\[\n/g' | \
    grep '"name"' | \
    awk -F\" '{print $4;}' | \
    sort -fu | \
    sed -e "s/^/${Repo}:/"
done
EOF
chmod +x /usr/bin/docker-tags

建立docker用户组:

Note: 默认情况下,docker 命令会使用 Unix socket 与 Docker 引擎通讯。而只有 root 用户和 docker 组的用户才可以访问 Docker 引擎的 Unix socket。出于安全考虑,一般 Linux 系统上不会直接使用 root 用户。因此,更好地做法是将需要使用 docker 的用户加入 docker 用户组。

#建立 docker 组:
groupadd docker
#将当前用户加入 docker 组:
usermod -aG docker $USER

卸载docker-ce:

yum remove docker-ce
rm -rf /var/lib/docker

设置docker监听tcp端口,以便远程访问:

cat >> /etc/docker/daemon.json <<'EOF'
{
"hosts": ["unix:///var/run/docker.sock", "tcp://127.0.0.1:2375"]
}
EOF
# check
netstat -lntp | grep dockerd

在之前的介绍中,我们知道镜像是 Docker 的三大组件之一。 Docker 运行容器前需要本地存在对应的镜像,如果本地不存在该镜像,Docker 会从镜像仓库下载该镜像。

一些术语:

  • 镜像:image 只读文件层,用于构造容器;
  • 镜像仓库:本地没有对应的镜像,docker会去外网拉取;
  • 镜像(仓库)加速器:由于网络原因,docker拉取镜像非常慢,国内有源,我们配上对应的地址就实现了加速;

https://gitbook.docker-practice.com/install/mirror

对于使用 systemd 的系统:

Note: 注意:如果您之前查看旧教程,修改了 docker.service 文件内容,请去掉您添加的内容(–registry-mirror=https://dockerhub.azk8s.cn)。

# 注意,一定要保证该文件符合 json 规范,否则 Docker 将不能启动。
cat >> /etc/docker/daemon.json <<'EOF'
 
{
  "registry-mirrors": [
    "https://dockerhub.azk8s.cn",
    "https://reg-mirror.qiniu.com"
  ]
}
EOF
 
# 重新启动服务。
systemctl daemon-reload
systemctl restart docker

3.1 获取镜像

从 Docker 镜像仓库获取镜像的命令是 docker pull。其命令格式为:

docker pull [选项] [Docker Registry 地址[:端口号]/]仓库名[:标签]

具体的选项可以通过 docker pull –help 命令看到,这里我们说一下镜像名称的格式:

  • Docker 镜像仓库地址:地址的格式一般是 <域名/IP>[:端口号]。默认地址是 Docker Hub。
  • 仓库名:如之前所说,这里的仓库名是两段式名称,即 <用户名>/<软件名>。对于 Docker Hub,如果不给出用户名,则默认为 library,也就是官方镜像。

3.2 列出镜像

https://gitbook.docker-practice.com/image/list

要想列出已经下载下来的镜像,可以使用 docker image ls 命令。

列表包含了 仓库名、标签、镜像 ID、创建时间 以及 所占用的空间。 其中仓库名、标签在之前的基础概念章节已经介绍过了。镜像 ID 则是镜像的唯一标识,一个镜像可以对应多个 标签。因此,在上面的例子中,我们可以看到 ubuntu:18.04 和 ubuntu:latest 拥有相同的 ID,因为它们对应的是同一个镜像。

3.3 删除本地镜像

如果要删除本地的镜像,可以使用 docker image rm 命令,其格式为:

docker image rm [选项] <镜像1> [<镜像2> ...]
  • 用 ID、镜像名、摘要删除镜像
  • 其中,<镜像> 可以是 镜像短 ID、镜像长 ID、镜像名 或者 镜像摘要。

3.4 利用commit 理解镜像构成

https://gitbook.docker-practice.com/image/commit

#生成一个容器
docker run --name webserver -d -p 80:80 nginx
#修改一个文件(自定义)
docker exec -it webserver bash
echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
exit
#将容器保存为镜像
docker commit webserver nginx:v2
#查看镜像
docker image ls nginx
#利用刚才保存的镜像来跑一个新版本的容器
docker run --name web2 -d -p 81:80 nginx:v2

为什么慎用通过docker commit来生成镜像:

docker diff webserver
 C /root
A /root/.bash_history
C /run
C /usr
C /usr/share
C /usr/share/nginx
C /usr/share/nginx/html
C /usr/share/nginx/html/index.html
C /var
C /var/cache
C /var/cache/nginx
A /var/cache/nginx/client_temp
A /var/cache/nginx/fastcgi_temp
A /var/cache/nginx/proxy_temp
A /var/cache/nginx/scgi_temp
A /var/cache/nginx/uwsgi_temp

实际上我们只修改了一个html而已,联通容器本身运行产生的变化全部被保存起来了,就这样都这么多文件产生了变化,更别说在容器里编译安装一个软件了.

Note: docker commit 命令除了学习之外,还有一些特殊的应用场合,比如被入侵后保存现场等。但是,不要使用 docker commit 定制镜像,定制镜像应该使用 Dockerfile 来完成。

Warning: 慎用 docker commit,因为这种方式生成的镜像也被称为 黑箱镜像,换句话说,就是除了制作镜像的人知道执行过什么命令、怎么生成的镜像,别人根本无从得知。而且,即使是这个制作镜像的人,过一段时间后也无法记清具体在操作的。虽然 docker diff 或许可以告诉得到一些线索,但是远远不到可以确保生成一致镜像的地步。这种黑箱镜像的维护工作是非常痛苦的。

Note: 利用commit生成的镜像,其中的文件会一直如影随形的跟着这个镜像,即使根本无法访问到。这会让镜像更加臃肿。

Note: 应该利用dockerfile来生成镜像

3.5 使用 Dockerfile 定制镜像

https://gitbook.docker-practice.com/image/build

docker commit 的学习中,我们可以了解到,镜像的定制实际上就是定制每一层所添加的配置、文件。如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,那么之前提及的无法重复的问题、镜像构建透明性的问题、体积的问题就都会解决。这个脚本就是 Dockerfile。

Dockerfile 是一个文本文件,其内包含了一条条的 指令(Instruction),每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。

最简单的一个Dockerfile:

FROM nginx
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html

这个 Dockerfile 很简单,一共就两行。涉及到了两条指令,FROM 和 RUN。

  • FROM 指定基础镜像,FROM 是必备的指令,并且必须是第一条指令;
  • RUN 指令是用来执行命令行命令的。由于命令行的强大能力,RUN 指令在定制镜像时是最常用的指令之一,其格式有两种:
    • shell 格式:RUN <命令>,就像直接在命令行中输入的命令一样。刚才写的 Dockerfile 中的 RUN 指令就是这种格式。
    • exec 格式:RUN [“可执行文件”, “参数1”, “参数2”],这更像是函数调用中的格式。

运行这个dockerfile:

docker build -t nginx:v3 .
 
Sending build context to Docker daemon  2.048kB
Step 1/2 : FROM nginx
 ---> f949e7d76d63
Step 2/2 : RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
 ---> Running in 0734cf05b19f
Removing intermediate container 0734cf05b19f
 ---> 3d8b54dc3c7a
Successfully built 3d8b54dc3c7a
Successfully tagged nginx:v3

3.6 镜像构建上下文(Context)

Docker 在运行时分为 Docker 引擎(也就是服务端守护进程)和客户端工具。Docker 的引擎提供了一组 REST API,被称为 Docker Remote API,而如 docker 命令这样的客户端工具,则是通过这组 API 与 Docker 引擎交互,从而完成各种功能。因此,虽然表面上我们好像是在本机执行各种 docker 功能,但实际上,一切都是使用的远程调用形式在服务端(Docker 引擎)完成。也因为这种 C/S 设计,让我们操作远程服务器的 Docker 引擎变得轻而易举。

当构建的时候,用户会指定构建镜像上下文的路径,docker build 命令得知这个路径后,会将路径下的所有内容打包,然后上传给 Docker 引擎。这样 Docker 引擎收到这个上下文包后,展开就会获得构建镜像所需的一切文件。

一般来说,应该会将 Dockerfile 置于一个空目录下,或者项目根目录下。如果该目录下没有所需文件,那么应该把所需文件复制一份过来。如果目录下有些东西确实不希望构建时传给 Docker 引擎,那么可以用 .gitignore 一样的语法写一个 .dockerignore,该文件是用于剔除不需要作为上下文传递给 Docker 引擎的。

3.7 其它 docker build 的用法

docker build 还支持从URL构建,比如可以直接从 Git repo 中构建:

$ docker build https://github.com/twang2218/gitlab-ce-zh.git#:11.1
 
Sending build context to Docker daemon 2.048 kB
Step 1 : FROM gitlab/gitlab-ce:11.1.0-ce.0
11.1.0-ce.0: Pulling from gitlab/gitlab-ce
aed15891ba52: Already exists
773ae8583d14: Already exists
...

用给定的 tar 压缩包构建:

下载这个包,并自动解压缩,以其作为上下文,开始构建。

# 下载并解压
docker build http://server/context.tar.gz
# 标准输入
docker build - < context.tar.gz

从标准输入中读取 Dockerfile 进行构建:

如果标准输入传入的是文本文件,则将其视为 Dockerfile,并开始构建。这种形式由于直接从标准输入中读取 Dockerfile 的内容,它没有上下文,因此不可以像其他方法那样可以将本地文件 COPY 进镜像之类的事情。

docker build - < Dockerfile
cat Dockerfile | docker build -

3.8 dockerfile详解

RUN,CMD,ENTRYPOINT区别: https://www.jianshu.com/p/f0a0f6a43907

指令:

  • COPY: COPY [–chown=<user>:<group>] [“<源路径1>”,… “<目标路径>”]
  • ADD: ADD 指令和 COPY 的格式和性质基本一致。但是在 COPY 基础上增加了一些功能。
    • 在 COPY 和 ADD 指令中选择的时候,可以遵循这样的原则,所有的文件复制均使用 COPY 指令,仅在需要自动解压缩的场合使用 ADD。
  • CMD: 允许用户指定容器的默认执行的命令。此命令会在容器启动且 docker run 没有指定其他命令时运行
    • Docker 不是虚拟机,容器中的应用都应该以前台执行,而不是像虚拟机、物理机里面那样,用 systemd 去启动后台服务,容器内没有后台服务的概念。
    • 对于容器而言,其启动程序就是容器应用进程,容器就是为了主进程而存在的,主进程退出,容器就失去了存在的意义,从而退出,其它辅助进程不是它需要关心的东西。
  • ENTRYPOINT:
  • ENV: ENV <key1>=<value1> <key2>=<value2>…
  • ARG:
  • VOLUME: 匿名卷, VOLUME /data (docker run不知能volume跑起来的容器,写入/data的数据都不会记录到容器存储层)
  • EXPOSE
    • 仅仅是声明容器打算使用什么端口而已,并不会自动在宿主进行端口映射
    • -p <宿主端口>:<容器端口>
  • WORKDIR: 指定工作目录(或者称为当前目录),以后各层的当前目录就被改为指定的目录,如该目录不存在,WORKDIR 会帮你建立目录
  • USER: USER 指令和 WORKDIR 相似,都是改变环境状态并影响以后的层。USER 只是帮助你切换到指定用户而已,这个用户必须是事先建立好的,否则无法切换。
  • HEALTHCHECK: 健康检查
    • HEALTHCHECK [选项] CMD <命令>:设置检查容器健康状况的命令
    • HEALTHCHECK NONE:如果基础镜像有健康检查指令,使用这行可以屏蔽掉其健康检查指令
    • 和 CMD, ENTRYPOINT 一样,HEALTHCHECK 只可以出现一次,如果写了多个,只有最后一个生效

3.9 dockerfile多阶段构建

3.10 镜像安全

clair是 coreos 开源的容器漏洞扫描工具,在容器逐渐普及的今天,容器镜像安全问题日益严重。clair 是目前少数的开源安全扫描工具,主要提供OS(centos,debian,ubuntu等)的软件包脆弱性扫描。clair既可以单机部署也可以部署到k8s上,并可以与现有的registry集成。harbor 很好的整合了 clair ,通过简单的UI就可以对上传的镜像扫描,还可以通过每天的定时扫描对所有镜像进行统一扫描.

Notary是一套docker镜像的签名工具, 用来保证镜像在pull,push和传输工程中的一致性和完整性。避免中间人攻击,避免非法的镜像更新和运行。

容器是 Docker 又一核心概念。

简单的说,容器是独立运行的一个或一组应用,以及它们的运行态环境。对应的,虚拟机可以理解为模拟运行的一整套操作系统(提供了运行态环境和其他系统环境)和跑在上面的应用。

# 启动
docker run ubuntu:18.04 /bin/echo 'Hello world'
docker run -t -i ubuntu:18.04 /bin/bash
#启动全部容器
docker restart $(docker ps -a -q)
# 停止
docker stop xxx
#进入容器
docker attach xxx
docker exec -it xxx bash
#导出
docker export 7691a814370e > ubuntu.tar
#导入容器快照
cat ubuntu.tar | docker import - test/ubuntu:v1.0
#删除容器(运行中的容器加-f)
docker rm [-f] xxx
# 清理所有处于终止状态的容器
docker container prune

#查找官方仓库中的镜像
docker search centos
#download
docker pull centos

根据是否是官方提供,可将镜像分为两类:

  • 一种是类似 centos 这样的镜像,被称为基础镜像或根镜像。这些基础镜像由 Docker 公司创建、验证、支持、提供。这样的镜像往往使用单个单词作为名字。
  • 还有一种类型,比如 tianon/centos 镜像,它是由 Docker Hub 的注册用户创建并维护的,往往带有用户名称前缀。可以通过前缀 username/ 来指定使用某个用户提供的镜像,比如 tianon 用户。

推送镜像:

#username就是docker账号
docker tag ubuntu:18.04 username/ubuntu:18.04
docker image ls
docker push username/ubuntu:18.04
docker search username

5.1 私有仓库

Question: docker registry与repository的区别:

  • Repository:本身是一个仓库,这个仓库里面可以放具体的镜像,是指具体的某个镜像的仓库,比如Tomcat下面有很多个版本的镜像,它们共同组成了Tomcat的Repository;
  • Registry:镜像的仓库,比如官方的是Docker Hub,它是开源的,也可以自己部署一个,Registry上有很多的Repository,Redis、Tomcat、MySQL等等Repository组成了Registry;
  • 对于仓库地址 dl.dockerpool.com/ubuntu 来说,dl.dockerpool.com 为registry,ubuntu为repository;

  • docker-registry 是Dcoker官方的一个私有仓库镜像,可以将本地的镜像(如dockerfile生成的)打tag后push到以Registry起的容器的私有仓库中以提高拉取镜像的效率;
  • 官方的 docker-registry 来实现私有仓库,registry其实就是一个镜像,直接docker run即可;
  • docker-registry只是对相关API的一个实现,没有图形化管理,权限管理等高级功能(商业版的有),非官方的实现有vmware的harbor,nexus3 等等,其功能更完善;
# 拉取registry镜像
docker pull registry
# 启动registry容器,默认仓库会被创建在容器的 /var/lib/registry, -v将上传的镜像放到本地的/data目录
docker run -d -p 5000:5000 -v /data:/var/lib/registry --restart=always --name registry registry

在私有仓库上传、搜索、下载镜像:

docker pull ubuntu
docker image ls
#tag   docker tag SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG]
 
docker tag ubuntu:latest 127.0.0.1:5000/ubuntu:latest
#push
docker push 127.0.0.1:5000/ubuntu:latest
#查看仓库中的镜像,tags
curl -s 127.0.0.1:5000/v2/_catalog | python -m json.tool
curl -s 127.0.0.1:5000/v2/ubuntu/tags/list | python -m json.tool
#先删除已有镜像,再尝试从私有仓库中下载这个镜像
docker image rm 127.0.0.1:5000/ubuntu:latest
docker pull 127.0.0.1:5000/ubuntu:latest
docker image ls

如果你不想使用 127.0.0.1:5000 作为仓库地址,比如想让本网段的其他主机也能把镜像推送到私有仓库。你就得把例如 192.168.199.100:5000 这样的内网地址作为私有仓库地址,这时你会发现无法成功推送镜像。 这是因为 Docker 默认不允许非 HTTPS 方式推送镜像。我们可以通过 Docker 的配置选项来取消这个限制,或者查看下一节配置能够通过 HTTPS 访问的私有仓库。

对于使用 systemd 的系统7),请在需要docker pull的机器上的 /etc/docker/daemon.json (如果文件不存在请新建该文件)中写入如下内容:

{
  "registry-mirrors": [
    "https://dockerhub.azk8s.cn",
    "https://reg-mirror.qiniu.com"
  ],
 
  "insecure-registries": [
    "172.17.2.241:5000"
  ]
}
# 重启docker
systemctl restart docker

注意:该文件必须符合 json 规范,否则 Docker 将不能启动。

5.2 私有仓库高级配置

上一节我们搭建了一个具有基础功能的私有仓库,本小节我们来使用 Docker Compose 搭建一个拥有权限认证TLS 的私有仓库

5.2.1 自签证书

这种自签证书自签最简单,需要把registry.crt放到每个docker host的/etc/docker/certs.d/registry.local:5000目录下:

# http pass
docker run --entrypoint htpasswd registry:2 -Bbn admin Mrco2222 > htpasswd
# TSL
openssl req \
  -newkey rsa:4096 -nodes \
  -x509 -days 365 \
  -keyout registry.key \
  -out registry.crt \
  -subj '/C=CN/ST=SiChuan/L=ChengDu/O=BBD/CN=registry.local'

5.2.2 自建CA来自签

这种方式就是上面多了创建自签CA的过程,但是最为规范:

  • 需要把ca.crt放到每个docker host的/etc/docker/certs.d/registry.local:5000目录下
#!/bin/bash
# 创建CA私钥,并自签
openssl req -x509 -newkey rsa:4096 -days 3650 -nodes \
        -subj '/C=CN/ST=SiChuan/L=ChengDu/O=BBD/CN=Docker Registry CA' \
        -keyout ca.key \
        -out ca.crt
# 预览CA证书
# openssl x509 -text -noout -in ca.crt | less
 
# 生成服务器相关文件(私钥,csr,crt)
# key
openssl genrsa -out registry.key 4096
# csr,CN=域名
openssl req -new \
        -subj '/C=CN/ST=SiChuan/L=ChengDu/O=BBD/CN=registry.local' \
        -key registry.key \
        -out registry.csr
# 用上面生成的CA来签署服务器证书
openssl x509 -req -days 3650 \
        -CA ca.crt \
        -CAkey ca.key \
        -in registry.csr \
        -out registry.crt \
        -set_serial 01 \
        -extensions server

5.2.3 启动registry

可以选择docker run,也可以通过docker-compose文件来启动,个人推荐compose。

docker-compose.yml
registry:
  restart: always
  image: registry:2
  ports:
    - 5000:5000
  environment:
    REGISTRY_HTTP_TLS_CERTIFICATE: /certs/registry.crt
    REGISTRY_HTTP_TLS_KEY: /certs/registry.key
    REGISTRY_AUTH: htpasswd
    REGISTRY_AUTH_HTPASSWD_PATH: /certs/htpasswd
    REGISTRY_AUTH_HTPASSWD_REALM: Registry Realm
  volumes:
    - /data:/var/lib/registry
    - /root/certs:/certs

5.2.4 测试

# hosts
echo 172.17.2.248  registry.local >> /etc/hosts
# 将本地的一个image push到registry.local
docker tag centos:latest 127.0.0.1:5000/centos:mrco
docker push 127.0.0.1:5000/centos:mrco
# 查看registry.local的images和tags
curl -sk -u admin https://127.0.0.1:5000/v2/_catalog
curl -sk -u admin https://127.0.0.1:5000/v2/centos/tags/list | jq
# 分别在本地和其他host上拉取
docker login registry.local:5000
docker pull 127.0.0.1:5000/centos:mrco

实现的方式多种,我个人还是推荐通过nginx反代来做:

server {
    client_max_body_size 0;
    listen 443 ssl;
    server_name registry.yourhost.com;
    ssl_certificate   /mnt/cert/your-cert.pem;
    ssl_certificate_key  /mnt/cert/your-cert.key;
    ssl_session_timeout 10m;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
    location /v2/ {
      auth_basic "Registry realm";
      auth_basic_user_file /etc/nginx/conf.d/nginx.htpasswd;
      proxy_read_timeout      900;
      proxy_connect_timeout   300;
      proxy_redirect          off;
      proxy_set_header    Host                $http_host;
      proxy_set_header    X-Real-IP           $remote_addr;
      proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;
      proxy_set_header  X-Forwarded-Proto $scheme;
      proxy_pass http://127.0.0.1:5000;
    }
}

5.3 harbor

官方的docker-registry由于没有比较完善的可视化界面,在生产环境下如果要搭建私有仓库,这时就需要harbor了。

VMware公司开源了企业级Registry项目Harbor,其的目标是帮助用户迅速搭建一个企业级的Docker registry 服务。它以Docker公司开源的registry 为基础,提供了管理UI, 基于角色的访问控制(Role Based Access Control),AD/LDAP集成、以及审计日志(Audit logging) 等企业用户需求的功能,同时还原生支持中文,对广大中国用户是一个好消息。

Harbor核心组件解释如下:

  • Proxy:他是一个nginx的前端代理,代理Harbor的registry,UI, token等服务。
  • db:负责储存用户权限、审计日志、Dockerimage分组信息等数据。
  • UI:提供图形化界面,帮助用户管理registry上的镜像, 并对用户进行授权。
  • jobsevice:jobsevice是负责镜像复制工作的,他和registry通信,从一个registry pull镜像然后push到另一个registry,并记录job_log。
  • Adminserver:是系统的配置管理中心附带检查存储用量,ui和jobserver启动时候回需要加载adminserver的配置。
  • Registry:镜像仓库,负责存储镜像文件。
  • Log:为了帮助监控Harbor运行,负责收集其他组件的log,供日后进行分析。

下载好 harbor离线包之后,直接解压,修改harbor.yml,主要修改了hostname以及harbor_admin_password,配置如下:

# 安装docker-compose
curl -L "https://github.com/docker/compose/releases/download/1.24.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/bin/docker-compose
chmod +x /usr/bin/docker-compose
docker-compose --version
curl -L https://raw.githubusercontent.com/docker/compose/1.24.1/contrib/completion/bash/docker-compose > /etc/bash_completion.d/docker-compose
# harbor
wget https://storage.googleapis.com/harbor-releases/release-1.9.0/harbor-offline-installer-v1.9.1.tgz
tar zxf harbor-offline-installer-v1.9.1.tgz
cd harbor/
修改harbor.yml,hostname,password等
./install.sh

使用harbor:

还是在客户端上配置insecure-registries

docker tag busybox:latest 192.168.137.66/library/busybox:latest
docker images
docker login 192.168.137.66 -u admin -p admin123
# harbor上创建一个私有仓库haha
docker push 172.17.2.181/haha/centos:7

harbor增加ssl证书支持

停止和启动:

docker-compose stop
docker-compose start

复制代码如果你想要改变 harbor 配置,你应该使用上面的命令先停止 harbor,然后修改 harbor.cfg,执行 prepare 生成新的配置,然后启动 harbor。整个过程如下:

docker-compose down -v
vim harbor.cfg
prepare
docker-compose up -d

harbor 管理

5.4 nexus 3

容器中管理数据主要有两种方式:数据卷volume,主机目录

docker inspect xxx输出的Mounts区段内有数据卷相关的信息.

6.1 数据卷

  • 数据卷(Volumes,是一个可供一个或多个容器使用的特殊目录,它绕过 UFS,可以提供很多有用的特性)
    • 数据卷 可以在容器之间共享和重用
    • 对 数据卷 的修改会立马生效
    • 对 数据卷 的更新,不会影响镜像
    • 数据卷 是被设计用来持久化数据的,它的生命周期独立于容器,Docker 不会在容器被删除后自动删除 数据卷,并且也不存在垃圾回收这样的机制来处理没有任何容器引用的 数据卷。
    • 数据卷 的使用,类似于 Linux 下对目录或文件进行 mount,镜像中的被指定为挂载点的目录中的文件会隐藏掉,能显示看的是挂载的数据卷

常用操作:

#创建一个数据卷
docker volume create my-vol
#查看所有的数据卷
docker volume ls
#查看指定 数据卷 的信息
docker volume inspect my-vol
#删除数据卷
docker volume rm my-vol
#删除容器的同时移除数据卷
docker rm -v xxx

在用 docker run 命令的时候,使用 –mount 标记来将 数据卷 挂载到容器里。在一次 docker run 中可以挂载多个 数据卷,下面创建一个名为 web 的容器,并加载一个 数据卷 到容器的 /webapp 目录:

docker run --name pt -dt  \
  # -v vol01:/app \
  --mount source=vol01,target=/app \
  centos /sbin/init
# 进入容器验证
docker exec -it pt bash
df -h
echo ooo > /app/1
cat /var/lib/docker/volumes/vol01/_data/1

6.2 挂载宿主机目录作为数据卷

挂载一个宿主机的目录到容器,并且可设置为只读,当然也可以挂载一个文件进去,比如时区文件/etc/localtime:

docker run --name pt -dt  \
  # -v vol01:/app \
  --mount type=bind,source=/app,target=/app,readonly \
  centos /sbin/init

7.1 docker和iptables

  • 在Linux上,Docker操纵iptables规则以提供网络隔离,您不应修改Docker插入iptables策略中的规则;
  • Docker的所有iptables规则都已添加到DOCKER chain中,不要手动操作该表,如果您需要添加在Docker规则之前加载的规则,请将它们添加到DOCKER-USER chain中,在Docker自动创建任何规则之前,将加载这些规则;

限制与Docker守护程序的连接:

#只允许某个网段访问
iptables -I DOCKER-USER -i eth0 ! -s 192.168.1.0/24 -j DROP
#只接受的IP地址范围
iptables -I DOCKER-USER -m iprange -i eth0 ! --src-range 192.168.1.1-192.168.1.3 -j DROP

7.2 Docker网络模式

docker的网络采用插件形式,有以下几种:

  • bridge
    • 默认网络类型(br0),docker引擎会创建一个veth对,一端连接到容器实例并命名为eth0,另一端连接到指定的网桥中(比如docker0),因此同在一个主机的容器实例由于连接在同一个网桥中,它们能够互相通信;
    • 容器创建时还会自动创建一条SNAT规则,用于容器与外部通信;
    • 如果用户使用了-p或者-Pe端口端口,还会创建对应的端口映射规则;
  • overlay
    • 为支持容器跨主机通信,Docker提供了 overlay driver,使用户可以创建基于VxLAN的overlay网络8)
    • overlay 网络需要一个 key-value 数据库用于保存网络状态信息,包括 Network、Endpoint、IP 等。Consul、Etcd 和 ZooKeeper 都是 Docker 支持的 key-vlaue 软件。
  • container: 指定与某个容器实例共享网络;
  • none:不设置网络,相当于容器内没有配置网卡,用户可以手动配置;
  • host:与宿主机共享网络,此时容器没有使用网络的namespace,宿主机的所有设备,如Dbus会暴露到容器中,因此存在安全隐患,也和host抢占端口;
#查看某个容器所用为了类型
docker container inspect xxx
#列出网络
docker network ls

7.2.1 bridge(默认)

  • 启动容器时,首先会在主机上创建一个docker0的虚拟网桥,需要bridge-utils包支持,相当于交换机,同时自动分配一对网卡设备,一半在容器(eth0),一半在宿主机,并且还关联到了docker0,从而进行连接;
  • 每创建一个容器启动时自动分配地址以后会生成iptables规则;

任何接口进来只要不从docker0出去,源地址任何网络地址,无论到达任何主机,都要做地址伪装,自动选择主机物理源地址:

iptables -t nat -vnL | grep docker
 
Chain POSTROUTING (policy ACCEPT 156 packets, 9745 bytes)
 pkts bytes target     prot opt in     out     source               destination         
   30  1886 MASQUERADE  all  --  *      !docker0  172.17.0.0/16        0.0.0.0/0   

bridge查看:

brctl show
 
bridge  name    bridge id       STP enabled interfaces
brq4f42285e-8b  8000.000c29e8da73   no      eth0
                                           tap87373210-e1
docker0           8000.024233cedda5 no      veth1fc99ac

7.2.2 overlay网络

#host1
docker swarm init
#host2执行
docker swarm join --token SWMTKN-1-3ww9xfy1w8opdd5rcn0a3s4ye3s4evnllyki9kne7oo1dpi2ia-4z7mvmuni39wp2bya7u4cynt8 10.20.90.97:2377
docker node ls
#host1创建overlay网络
docker network create -d overlay --attachable prod-overlay
#host1创建容器并加入overlay网络
docker run --name testc1 --network prod-overlay -itd busybox
#host2同样
docker run --name testc2 --network prod-overlay -itd busybox
#host2查看
docker network ls
#相互ping
docker exec testc1 ping -c 2 testc2
docker exec testc2 ping -c 2 testc1

7.2.3 container

  • 与另一个运行得容器共用一个网络Network Namespace
  • –network=container:容器ID
  • ip都是相同的,同样谁先占用某个端口就是谁的

7.2.4 host

与宿主机共用一个网络。

# 使用后不需要做端口映射,性能最高,端口谁先占用就是谁的
docker run --network=host -d phpwind:v1

7.2.5 none

不为容器配置任何网络功能,没有网络适合联系使用,只有基础命令:

docker run --network=none -d phpwind:v1 /bin/bash

7.3 外部访问容器

https://gitbook.docker-practice.com/network/port_mapping

容器中可以运行一些网络应用,要让外部也可以访问这些应用,可以通过 -P 或 -p 参数来指定端口映射:

  • -P, 随机映射一个 49000~49900 的端口到内部容器开放的全部网络端口
  • -p, 一对一指定端口映射关系,多个端口就指定多个-p

7.4 容器互联

随着 Docker 网络的完善,强烈建议大家将容器加入自定义的 Docker 网络来连接多个容器,而不是使用 –link 参数。

docker network create -d bridge my-net
docker run -itd --name centos1 --network my-net centos
docker run -itd --name centos2 --network my-net centos
#在其中一个容器内ping对方
docker exec -it centos1 bash
    ping centos2

7.5 配置DNS

如何自定义配置容器的主机名和 DNS 呢?秘诀就是 Docker 利用虚拟文件来挂载容器的3个相关配置文件。

在容器中使用 mount 命令可以看到挂载信息:

$ mount
/dev/disk/by-uuid/1fec...ebdf on /etc/hostname type ext4 ...
/dev/disk/by-uuid/1fec...ebdf on /etc/hosts type ext4 ...
tmpfs on /etc/resolv.conf type tmpfs ...

这种机制可以让宿主主机 DNS 信息发生更新后,所有 Docker 容器的 DNS 配置通过 /etc/resolv.conf 文件立刻得到更新。 配置全部容器的 DNS ,也可以在 /etc/docker/daemon.json 文件中增加以下内容来设置:

{
  "dns" : [
    "114.114.114.114",
    "119.29.29.29"
  ]
}

这样每次启动的容器 DNS 自动配置为 114.114.114.114 和 119.29.29.29。使用以下命令来证明其已经生效。

docker run -it –rm ubuntu:18.04 cat etc/resolv.conf

如果用户想要手动指定容器的配置,可以在使用 docker run 命令启动容器时加入如下参数:

  • -h HOSTNAME 或者 –hostname=HOSTNAME 设定容器的主机名,它会被写到容器内的 /etc/hostname 和 /etc/hosts。但它在容器外部看不到,既不会在 docker container ls 中显示,也不会在其他的容器的 /etc/hosts 看到。
  • –dns=IP_ADDRESS 添加 DNS 服务器到容器的 /etc/resolv.conf 中,让容器用这个服务器来解析所有不在 /etc/hosts 中的主机名。
  • –dns-search=DOMAIN 设定容器的搜索域,当设定搜索域为 .example.com 时,在搜索一个名为 host 的主机时,DNS 不仅搜索 host,还会搜索 host.example.com。

注意:如果在容器启动时没有指定最后两个参数,Docker 会默认用主机上的 /etc/resolv.conf 来配置容器。

https://mrbird.cc/Docker-Compose.html

demo1.yml
version: '2.3'
services:
  captchabj_v1_0:
    image: registry.xbits.net/algorithm/captcha:1.1
    volumes:
    - /data2/ml/captcha-bjtax:/opt/captcha-bjtax
    ports:
      - 51121:51121
    working_dir: /opt/captcha-bjtax
    command: /root/anaconda2/bin/python server_mxnet_bjtax.py
    logging:
      driver: none
    networks:
    - gpunetwork
networks:
  gpunetwork:
    external: true
demo2.yml
---
version: "2"
services:
  transmission:
    image: linuxserver/transmission
    container_name: transmission
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Europe/London
      - TRANSMISSION_WEB_HOME=/combustion-release/ #optional
      - USER=username #optional
      - PASS=password #optional
    volumes:
      - path to data:/config
      - path to downloads:/downloads
      - path to watch folder:/watch
    ports:
      - 9091:9091
      - 51413:51413
      - 51413:51413/udp
    restart: unless-stopped

就是在docker本身daemon上pull一个image,然后跑一个容器,通过这个容器来管理docker.

目前docker官方推荐的管理其docker daemon的程序就是portainer和rancher.

9.1 rancher

  • Rancher 1.x 版本是基于 Docker 以 Cattle 为调度引擎的容器管理平台
  • Rancher 2.x 版本基于Kubernetes 基础上重新设计,保留了 1.x 版本中的友好功能,同时提供了更多新的功能
    • 内置 CI/CD 流水线
    • 告警和日志收集功能
    • 多集群管理功能
    • 集成 Rancher Kubernetes Engine (RKE)
    • 与各云 Kubernetes 服务(如 GKE、EKS、AKS) 集成
docker run -d --name rancher --restart=unless-stopped -p 80:80 -p 443:443 rancher/rancher

9.2 portainer管理平台

安装也很简单:

docker volume create portainer_data
docker run -d --name portainer -p 8000:8000 -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer

管理本机的话,打开portainer直接添加local类型的endpoint就行,添加远程docker的话,需要修改下远端docker服务的配置:

vim /usr/lib/systemd/system/docker.service
ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2375 -H fd:// --containerd=/run/containerd/containerd.sock
#重启docker服务
systemctl daemon-reload
systemctl restart docker


1)
社区免费版,支持周期7个月
2)
EE 即企业版,强调安全,功能齐全,付费使用,支持周期24个月
3)
旧版本的Docker称为docker,docker.io,docker-engine 新版称为docker-ce
4)
centos-extras源必须打开,如果你关闭了切记打开
5)
执行这个命令后,脚本就会自动的将一切准备工作做好,并且把 Docker CE 的稳定(stable)版本安装在系统中。
6)
比如centos有6,7,8版本,还有更细的版本,我怎么知道具体要pull哪个tag对吧,这么实用的功能官方没有提供简直说不过去!
7)
Ubuntu 16.04+, Debian 8+, centos 7
8)
VxLAN 可将二层数据封装到 UDP 进行传输,VxLAN 提供与 VLAN 相同的以太网二层服务,但是拥有更强的扩展性和灵活性。
  • virtualization/docker/dive_into_docker.txt
  • 最后更改: 2020/08/05 03:45
  • (外部编辑)