xdocker

Docker 简介

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

runc 是一个 Linux 命令行工具,用于根据 OCI容器运行时规范 创建和运行容器。

containerd 是一个守护程序,它管理容器生命周期,提供了在一个节点上执行容器和管理镜像的最小功能集。

为什么要使用 Docker?

  • 更高效的利用系统资源
  • 更快速的启动时间
  • 一致的运行环境
  • 持续交付和部署
  • 更轻松的迁移
  • 更轻松的维护和扩展

基本概念 镜像 容器 仓库

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

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

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

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

安装 Docker

Docker 分为 CE 和 EE 两大版本。CE 即社区版(免费,支持周期 7 个月),EE 即企业版,强调安全,付费使用,支持周期 24 个月。

Docker CE 分为 stable test 和 nightly 三个更新频道。

在线安装

cat << 'EOF' > /etc/yum.repos.d/docker-ce.repo
[docker-ce-stable]
name=Docker CE Stable - $basearch
baseurl=https://download.docker.com/linux/centos/7/$basearch/stable
enabled=1
gpgcheck=0
#gpgkey=https://download.docker.com/linux/centos/gpg
EOF

阿里云仓库 http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

yum list docker-ce --showduplicates | sort -r

yum install docker-ce-18.09.9 docker-ce-cli-18.09.9 containerd.io
OR  yum install docker-ce-<VERSION_STRING> docker-ce-cli-<VERSION_STRING> containerd.io
OR  yum install docker-ce docker-ce-cli containerd.io

systemctl start docker
systemctl status docker
systemctl enable docker


==========================================================================================================================================================================================================
 Package                                               Arch                                  Version                                                Repository                                       Size
==========================================================================================================================================================================================================
Installing:
 containerd.io                                         x86_64                                1.2.13-3.2.el7                                         docker-ce-stable                                 25 M
 docker-ce                                             x86_64                                3:18.09.9-3.el7                                        docker-ce-stable                                 21 M
 docker-ce-cli                                         x86_64                                1:18.09.9-3.el7                                        docker-ce-stable                                 16 M
Installing for dependencies:
 audit-libs-python                                     x86_64                                2.8.5-4.el7                                            base                                             76 k
 checkpolicy                                           x86_64                                2.5-8.el7                                              base                                            295 k
 container-selinux                                     noarch                                2:2.119.1-1.c57a6f9.el7                                extras                                           40 k
 libcgroup                                             x86_64                                0.41-21.el7                                            base                                             66 k
 libsemanage-python                                    x86_64                                2.5-14.el7                                             base                                            113 k
 policycoreutils-python                                x86_64                                2.5-34.el7                                             base                                            457 k
 python-IPy                                            noarch                                0.75-6.el7                                             base                                             32 k
 setools-libs                                          x86_64                                3.3.8-4.el7                                            base                                            620 k

Transaction Summary      Install  3 Packages (+8 Dependent packages)
==========================================================================================================================================================================================================

离线安装

https://download.xp.cn/install.sh
https://download.docker.com/linux/static/stable/x86_64/
https://docs.docker.com/engine/install/binaries/
https://download.docker.com/linux/static/stable/x86_64/docker-18.09.9.tgz
http://192.168.1.8/chfs/shared/linux-pkg/docker-18.09.9.tgz
https://docs.docker.com/v18.09/compose/install/
https://github.com/docker/compose/releases/tag/1.24.1
https://github.com/docker/compose/releases/download/1.24.1/docker-compose-Linux-x86_64

md5sum  docker-18.09.9.tgz
b596ac0dcd64328554efc19c0aa2832a  docker-18.09.9.tgz


# cd /as4k/ && wget http://192.168.1.8/chfs/shared/linux-pkg/docker-18.09.9.tgz
# tar xf docker-18.09.9.tgz
# ls docker
containerd  containerd-shim  ctr  docker  dockerd  docker-init  docker-proxy  runc
# cd docker/   && cp containerd  containerd-shim  ctr  docker  dockerd  docker-init  docker-proxy  runc /usr/bin
# cd /usr/bin/ && ls containerd  containerd-shim  ctr  docker  dockerd  docker-init  docker-proxy  runc
# cd /as4k/ && wget http://192.168.1.8/chfs/shared/linux-pkg/docker-compose-Linux-x86_64
# cp docker-compose-Linux-x86_64 /usr/bin/docker-compose
# chmod +x /usr/bin/docker-compose
# ls -l /usr/bin/docker-compose
-rwxr-xr-x 1 root root 16168192 Apr 14 23:45 /usr/bin/docker-compose



cat << 'EOF' > /usr/lib/systemd/system/docker.service
[Unit]
Description=Docker Application Container Engine
Documentation=https://docs.docker.com
#BindsTo=containerd.service
#After=network-online.target firewalld.service containerd.service
After=network-online.target firewalld.service
Wants=network-online.target
#Requires=docker.socket
[Service]
Type=notify
ExecStart=/usr/bin/dockerd
#ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
ExecReload=/bin/kill -s HUP $MAINPID
TimeoutSec=0
RestartSec=2
Restart=always
StartLimitBurst=3
StartLimitInterval=60s
LimitNOFILE=infinity
LimitNPROC=infinity
LimitCORE=infinity
TasksMax=infinity
Delegate=yes
KillMode=process
[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl restart docker
systemctl enable docker
systemctl status docker

docker version
docker-compose version


[root@node4 as4k]# docker-compose version
docker-compose version 1.24.1, build 4667896b
docker-py version: 3.7.3
CPython version: 3.6.8
OpenSSL version: OpenSSL 1.1.0j  20 Nov 2018
[root@node4 as4k]# docker version
Client: Docker Engine - Community
 Version:           18.09.9
 API version:       1.39
 Go version:        go1.11.13
 Git commit:        039a7df9ba
 Built:             Wed Sep  4 16:50:02 2019
 OS/Arch:           linux/amd64
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          18.09.9
  API version:      1.39 (minimum version 1.12)
  Go version:       go1.11.13
  Git commit:       039a7df9ba
  Built:            Wed Sep  4 16:55:50 2019
  OS/Arch:          linux/amd64
  Experimental:     false

[root@node4 as4k]# docker info | grep "Docker Root Dir" 2> /dev/null
Docker Root Dir: /var/lib/docker

添加内核参数

如何安装后使用 docker info 有如下警告:

可以添加如下内核参数消除之

cat << 'EOF' >> /etc/sysctl.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
sysctl -p

配置docker镜像加速器

镜像加速器,相当于国内厂商了克隆一份docker官方镜像仓库

网易云 https://www.163yun.com/help/documents/56918246390157312
阿里云 https://cr.console.aliyun.com/cn-hangzhou/mirrors
https://registry.docker-cn.com

touch /etc/docker/daemon.json
cat /etc/docker/daemon.json
{
  "registry-mirrors": [
    "https://hub-mirror.c.163.com"
  ]
}

systemctl daemon-reload && systemctl restart docker

检查加速器配置是否生效
[root@node4 ~]# docker info | grep -i -A1 "mirrors"
Registry Mirrors:
 https://hub-mirror.c.163.com/

简单运行体验

docker pull dockerpracticesig/docker_practice && docker run -it --rm -p 4000:80 dockerpracticesig/docker_practice
docker run -p 80:80 nginx
curl 192.168.1.118

[root@node4 ~]# docker ps
CONTAINER ID        IMAGE                               COMMAND                  CREATED              STATUS              PORTS                  NAMES
10fe5947508d        nginx                               "nginx -g 'daemon of…"   About a minute ago   Up About a minute   0.0.0.0:80->80/tcp     unruffled_margulis
a0f262e37a96        dockerpracticesig/docker_practice   "nginx -g 'daemon of…"   12 minutes ago       Up 12 minutes       0.0.0.0:4000->80/tcp   elated_cori

docker rm -fv 10fe5947508d a0f262e37a96

镜像 image

docker images --help
docker image --help

获取镜像
docker pull --help
docker pull [选项] [Docker Registry 地址[:端口号]/]仓库名[:标签]

仓库名:这里的仓库名是两段式名称,即 <用户名>/<软件名>。
对于 Docker Hub,如果不给出用户名,则默认为 library,也就是官方镜像。

示例
docker pull daocloud.io/library/mysql:5.7.4
docker pull daocloud.io/daocloud/dao-wordpress:latest
docker pull daocloud.io/daocloud/dao-wordpress  (没指定版本号默认是latest)

列出已经下载下的镜像
docker images  OR  docker image ls

查看镜像、容器、数据卷所占用的空间
docker system df

虚悬镜像

这个镜像既没有仓库名,也没有标签,均为 <none>
<none>               <none>              00285df0df87        5 days ago          342 MB

这个镜像原本是有镜像名和标签的,原来为 mongo:3.2,随着官方镜像维护,发布了新版本后,重新 docker pull mongo:3.2 时,mongo:3.2 这个镜像名被转移到了新下载的镜像身上,而旧的镜像上的这个名称则被取消,从而成为了 <none>。除了 docker pull 可能导致这种情况,docker build 也同样可以导致这种现象。由于新旧镜像同名,旧镜像名称被取消,从而出现仓库名、标签均为 <none> 的镜像。这类无标签镜像也被称为 虚悬镜像(dangling image) ,可以用下面的命令专门显示这类镜像:

docker image ls -f dangling=true

一般来说,虚悬镜像已经失去了存在的价值,是可以随意删除的,可以用下面的命令删除。
docker image prune
一般比较稳妥的执行场景是,我们基于容器的最新服务正在运行,此时执行 docker image prune 是肯定不会删除正在使用的镜像的,因为正在使用的镜像不会被删除

删除镜像

docker image rm --help

$ docker image ls
REPOSITORY                  TAG                 IMAGE ID            CREATED             SIZE
centos                      latest              0584b3d2cf6d        3 weeks ago         196.5 MB
redis                       alpine              501ad78535f0        3 weeks ago         21.03 MB
docker                      latest              cf693ec9b5c7        3 weeks ago         105.1 MB
nginx                       latest              e43d811ce2f4        5 weeks ago         181.5 MB

docker image rm 501  (短ID形式,只要足够区分不同的镜像即可)
docker image rm centos:latest

更精确的是使用 镜像摘要 删除镜像
[root@node4 ~]# docker image ls --digests
REPOSITORY                          TAG                 DIGEST                                                                    IMAGE ID            CREATED             SIZE
dockerpracticesig/docker_practice   latest              sha256:38bf7761cb4d060919f24ed96686e4a01dcb3a09d26abd786e5ea2f22fc9b9e7   68969b7a04b7        3 days ago          46.8MB
nginx                               latest              sha256:282530fcb7cd19f3848c7b611043f82ae4be3781cb00105a1d593d7e6286b596   ed21b7a8aee9        2 weeks ago         127MB

docker image rm sha256:38bf7761cb4d060919f24ed96686e4a01dcb3a09d26abd786e5ea2f22fc9b9e7
我们平常可能用不到这种删除镜像的方式,但是某些镜像管理服务如harbor,nexus在删除镜像时可能会用到这种方式,在相关日志里就会体现镜像摘要

删除所有镜像
docker image rm $(docker image ls -q)

动态固化容器为镜像 docker 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 diff webserver

现在我们定制好了变化,我们希望能将其保存下来形成镜像。
定制镜像前执行以下 yum clean all 可以大幅缩小镜像尺寸
docker commit [选项] <容器ID或容器名> [<仓库名>[:<标签>]]
docker commit --author "Tao Wang <twang2218@gmail.com>" --message "修改了默认网页" webserver nginx:v2

我们可以在 docker image ls 中看到这个新定制的镜像

docker run --name web2 -d -p 81:80 nginx:v2

docker commit 意味着所有对镜像的操作都是黑箱操作,生成的镜像也被称为 黑箱镜像,会让后续镜像维护变的困难,因此不是正式制作镜像的方案,不过在一些测试场景下使用还是非常方便的

离线传输镜像 镜像导入导出

docker save --help
docker load --help

docker save -o nginx-v3.tar nginx:v3
docker image rm nginx:v3
docker load -i nginx-v3.tar

使用 Dockerfile 定制镜像

专业定制镜像的方案,就是使用一份 Dockerfile 文件,一般我们会基于已有的镜像来定制,比如基于centos镜像来定制,在里面安装上特有的服务,以为我所用,当然也可以根据空白镜像来定制

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

快速体验

# mkdir -p /root/mynginx && cd /root/mynginx
# cat << 'EOF' > Dockerfile
FROM nginx
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
EOF

而 FROM 就是指定 基础镜像,是必备的指令,并且必须是第一条指令
Docker 还存在一个特殊的镜像,名为 scratch。这个镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像

不以任何系统为基础,直接将可执行文件复制进镜像的做法并不罕见,比如 swarm、etcd。对于 Linux 下静态编译的程序来说,并不需要有操作系统提供运行时支持,所需的一切库都已经在可执行文件里了,因此直接 FROM scratch 会让镜像体积更加小巧。使用 Go 语言 开发的应用很多会使用这种方式来制作镜像,这也是为什么有人认为 Go 是特别适合容器微服务架构的语言的原因之一

RUN 指令是用来执行命令行命令的。由于命令行的强大能力,RUN 指令在定制镜像时是最常用的指令之一。其格式有两种:
shell 格式:RUN <命令>
    RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
exec 格式:RUN ["可执行文件", "参数1", "参数2"],这更像是函数调用中的格式
每一个 RUN 都是启动一个容器、执行命令、然后提交存储层文件变更

构建镜像
docker build -t nginx:v3 .

[root@node4 mynginx]# docker images | grep v3
nginx                               v3                  d70319409a81        13 seconds ago      127MB

docker run -it --rm -p 80:80 nginx:v3

COPY 复制文件

COPY 复制文件
COPY [--chown=<user>:<group>] <源路径>... <目标路径>
COPY package.json /usr/src/app/
COPY hom* /mydir/
目标路径不需要事先创建,如果目录不存在会在复制文件前先行创建缺失目录。
使用 COPY 指令,源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等

CMD and ENTRYPOINT

########################################################################################################################
CMD 容器启动命令
shell 格式:CMD <命令>
exec 格式:CMD ["可执行文件", "参数1", "参数2"...]
参数列表格式:CMD ["参数1", "参数2"...]。在指定了 ENTRYPOINT 指令后,用 CMD 指定具体的参数。
Docker 不是虚拟机,容器就是进程。既然是进程,那么在启动容器的时候,需要指定所运行的程序及参数。CMD 指令就是用于指定默认的容器主进程的启动命令的。
Docker 不是虚拟机,容器中的应用都应该以前台执行,而不是像虚拟机、物理机里面那样,用 systemd 去启动后台服务,容器内没有后台服务的概念。
对于容器而言,其启动程序就是容器应用进程,容器就是为了主进程而存在的,主进程退出,容器就失去了存在的意义,从而退出,其它辅助进程不是它需要关心的东西。
CMD ["nginx", "-g", "daemon off;"]
CMD [ "sh", "-c", "echo $HOME" ]


########################################################################################################################
ENTRYPOINT 入口点
ENTRYPOINT 的目的和 CMD 一样,都是在指定容器启动程序及参数。ENTRYPOINT 在运行时也可以替代,不过比 CMD 要略显繁琐,需要通过 docker run 的参数 --entrypoint 来指定
当指定了 ENTRYPOINT 后,CMD 的含义就发生了改变,不再是直接的运行其命令,而是将 CMD 的内容作为参数传给 ENTRYPOINT 指令,换句话说实际执行时,将变为:
    <ENTRYPOINT> "<CMD>"
那么有了 CMD 后,为什么还要有 ENTRYPOINT 呢?这种 <ENTRYPOINT> "<CMD>" 有什么好处么?让我们来看几个场景

场景一:让镜像变成像命令一样使用

mkdir -p /root/getip && cd /root/getip
cat << 'EOF' > Dockerfile
FROM daocloud.io/library/ubuntu:18.04
RUN apt-get update \
    && apt-get install -y curl \
    && rm -rf /var/lib/apt/lists/*
CMD [ "curl", "-s", "cip.cc" ]
EOF
docker build -t ubuntu:v1 .
docker run -it --rm ubuntu:v1
docker run -it --rm ubuntu:v1 ls -rlht  /
docker run -it --rm ubuntu:v1 bash


mkdir -p /root/getip2 && cd /root/getip2
cat << 'EOF' > Dockerfile
FROM ubuntu:18.04
RUN apt-get update \
    && apt-get install -y curl \
    && rm -rf /var/lib/apt/lists/*
ENTRYPOINT [ "curl", "-s", "cip.cc" ]
EOF
docker build -t ubuntu:v2 .
docker run -it --rm ubuntu:v2
docker run -it --rm ubuntu:v2 -i
########################################################################################################################

ENV 设置环境变量

格式有两种:
    ENV <key> <value>
    ENV <key1>=<value1> <key2>=<value2>...


ENV VERSION=1.0 DEBUG=on \
    NAME="Happy Feet"

定义了环境变量,那么在后续的指令中,就可以使用这个环境变量。比如在官方 node 镜像 Dockerfile 中,就有类似这样的代码:

ENV NODE_VERSION 7.2.0
RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz" \
  && curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" \
  && gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \
  && grep " node-v$NODE_VERSION-linux-x64.tar.xz\$" SHASUMS256.txt | sha256sum -c - \
  && tar -xJf "node-v$NODE_VERSION-linux-x64.tar.xz" -C /usr/local --strip-components=1 \
  && rm "node-v$NODE_VERSION-linux-x64.tar.xz" SHASUMS256.txt.asc SHASUMS256.txt \
  && ln -s /usr/local/bin/node /usr/local/bin/nodejs

下列指令可以支持环境变量展开: ADD、COPY、ENV、EXPOSE、FROM、LABEL、USER、WORKDIR、VOLUME、STOPSIGNAL、ONBUILD、RUN。

EXPOSE 声明端口

格式为 EXPOSE <端口1> [<端口2>...]
EXPOSE 指令是声明运行时容器提供服务端口,这只是一个声明,在运行时并不会因为这个声明应用就会开启这个端口的服务。在 Dockerfile 中写入这样的声明有两个好处,一个是帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射;另一个用处则是在运行时使用随机端口映射时,也就是 docker run -P 时,会自动随机映射 EXPOSE 的端口。

要将 EXPOSE 和在运行时使用 -p <宿主端口>:<容器端口> 区分开来。-p,是映射宿主端口和容器端口,换句话说,就是将容器的对应端口服务公开给外界访问,而 EXPOSE 仅仅是声明容器打算使用什么端口而已,并不会自动在宿主进行端口映射

WORKDIR 指定工作目录

格式为 WORKDIR <工作目录路径>
使用 WORKDIR 指令可以来指定工作目录(或者称为当前目录),以后各层的当前目录就被改为指定的目录,如该目录不存在,WORKDIR 会帮你建立目录。

之前提到一些初学者常犯的错误是把 Dockerfile 等同于 Shell 脚本来书写,这种错误的理解还可能会导致出现下面这样的错误:
RUN cd /app
RUN echo "hello" > world.txt

如果将这个 Dockerfile 进行构建镜像运行后,会发现找不到 /app/world.txt 文件,或者其内容不是 hello。原因其实很简单,在 Shell 中,连续两行是同一个进程执行环境,因此前一个命令修改的内存状态,会直接影响后一个命令;而在 Dockerfile 中,这两行 RUN 命令的执行环境根本不同,是两个完全不同的容器。这就是对 Dockerfile 构建分层存储的概念不了解所导致的错误。

之前说过每一个 RUN 都是启动一个容器、执行命令、然后提交存储层文件变更。第一层 RUN cd /app 的执行仅仅是当前进程的工作目录变更,一个内存上的变化而已,其结果不会造成任何文件变更。而到第二层的时候,启动的是一个全新的容器,跟第一层的容器更完全没关系,自然不可能继承前一层构建过程中的内存变化。

每一个 RUN 都是启动一个容器、执行命令、然后提交存储层文件变更

USER 指定当前用户

格式:USER <用户名>[:<用户组>]

USER 指令和 WORKDIR 相似,都是改变环境状态并影响以后的层。WORKDIR 是改变工作目录,USER 则是改变之后层的执行 RUN, CMD 以及 ENTRYPOINT 这类命令的身份。

当然,和 WORKDIR 一样,USER 只是帮助你切换到指定用户而已,这个用户必须是事先建立好的,否则无法切换。

RUN groupadd -r redis && useradd -r -g redis redis
USER redis
RUN [ "redis-server" ]

如果以 root 执行的脚本,在执行期间希望改变身份,比如希望以某个已经建立好的用户来运行某个服务进程,不要使用 su 或者 sudo,这些都需要比较麻烦的配置,而且在 TTY 缺失的环境下经常出错。建议使用 gosu。

# 建立 redis 用户,并使用 gosu 换另一个用户执行命令
RUN groupadd -r redis && useradd -r -g redis redis
# 下载 gosu
RUN wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/1.7/gosu-amd64" \
    && chmod +x /usr/local/bin/gosu \
    && gosu nobody true
# 设置 CMD,并以另外的用户执行
CMD [ "exec", "gosu", "redis", "redis-server" ]

HEALTHCHECK 健康检查

HEALTHCHECK [选项] CMD <命令>:设置检查容器健康状况的命令
HEALTHCHECK NONE:如果基础镜像有健康检查指令,使用这行可以屏蔽掉其健康检查指令

当在一个镜像指定了 HEALTHCHECK 指令后,用其启动容器,初始状态会为 starting,在 HEALTHCHECK 指令检查成功后变为 healthy,如果连续一定次数失败,则会变为 unhealthy

和 CMD, ENTRYPOINT 一样,HEALTHCHECK 只可以出现一次,如果写了多个,只有最后一个生效。

在 HEALTHCHECK [选项] CMD 后面的命令,格式和 ENTRYPOINT 一样,分为 shell 格式,和 exec 格式。命令的返回值决定了该次健康检查的成功与否:0:成功;1:失败;2:保留,不要使用这个值。

FROM nginx
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
HEALTHCHECK --interval=5s --timeout=3s \
  CMD curl -fs http://localhost/ || exit 1

为了帮助排障,健康检查命令的输出(包括 stdout 以及 stderr)都会被存储于健康状态里,可以用 docker inspect 来查看。

ONBUILD 为他人做嫁衣裳

待更新

参考文档

Dockerfie   官方文档:         https://docs.docker.com/engine/reference/builder/
Dockerfile  最佳实践文档:     https://docs.docker.com/develop/develop-images/dockerfile_best-practices/
Docker    官方镜像 Dockerfile:https://github.com/docker-library/docs

容器 container

docker container --help

docker run -it ubuntu:18.04 /bin/bash
-i, --interactive                    Keep STDIN open even if not attached
-t, --tty                            Allocate a pseudo-TTY

其中,-t 选项让Docker分配一个伪终端(pseudo-tty)并绑定到容器的标准输入上, -i 则让容器的标准输入保持打开。

当利用 docker run 来创建容器时,Docker 在后台运行的标准操作包括:
    检查本地是否存在指定的镜像,不存在就从公有仓库下载
    利用镜像创建并启动一个容器
    分配一个文件系统,并在只读的镜像层外面挂载一层可读写层
    从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中去
    从地址池配置一个 ip 地址给容器
    执行用户指定的应用程序
    执行完毕后容器被终止

后台运行 -d ,可以 docker logs container-id 查看日志
docker run -d ubuntu:18.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"

终止容器  docker container stop [OPTIONS] CONTAINER [CONTAINER...]

docker logs --tail 20 CONTAINER  查看最后20行日志

进入容器 执行命令
docker exec -it CONTAINER bash
docker exec -it CONTAINER cat /etc/os-release


运行中的容器导出一个包  传输到另一个机器上可以重新导入成为一个镜像
# docker container --help | grep "export"
  export      Export a container's filesystem as a tar archive
# docker image --help | grep "import"
  import      Import the contents from a tarball to create a filesystem image


删除容器
docker container rm CONTAINER
docker container rm -fv CONTAINER
docker container prune

列出已运行的容器
docker ps -a         OR           docker container ls

仓库 repository registry

Docker Registry 公开服务

国外常用仓库
https://hub.docker.com/                               大量的高质量的官方镜像
https://quay.io/repository/                           CoreOS 相关的镜像存储在这里
https://cloud.google.com/container-registry/          Kubernetes 的镜像

由于某些原因,在国内访问这些服务可能会比较慢。国内的一些云服务商提供了针对 Docker Hub 的镜像服务(Registry Mirror),这些镜像服务被称为加速器
https://cr.console.aliyun.com/#/accelerator           阿里云加速器
https://www.daocloud.io/mirror#accelerator-doc        DaoCloud 加速器

国内也有一些云服务商提供类似于 Docker Hub 的公开服务
https://c.163.com/hub#/m/library/              网易云镜像服务
https://hub.daocloud.io/                       DaoCloud 镜像市场
https://cr.console.aliyun.com/                 阿里云镜像库

私有 Docker Registry

https://hub.docker.com/_/registry/                    Docker 官方提供
https://docs.docker.com/datacenter/dtr/2.0/           官方的商业化版本 Docker Trusted Registry

除了官方的 Docker Registry 外,还有第三方软件实现了 Docker Registry API,甚至提供了用户界面以及一些高级功能
比如,Harbor 和 Sonatype Nexus。

上传下载镜像

不论是公有还是私有镜像仓库,都是拿来存储镜像的 比如我们可以在 hub.docker.com 注册账号,然后在本地登陆,之后就可以推送镜像到公网仓库上,最后也可以退出登陆,需要用到的docker命令如下

docker login -uusername -ppassword SERVER
docker tag SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG]
docker push NAME[:TAG]

上面的步骤完成之后,我们就可以在其它机器上 docker pull 下载镜像了

有些仓库是需要登陆账号密码才能下载,而有些是可以直接下载,第1次登陆账号密码后,后续下载镜像则不用在输入账号密码,因此如果我们不想系统记录住我们的账号密码可以 docker logout 退出登陆

相信大家可以看到,上面一系列操作的目的就是为了分享镜像用的,如果我们需要分享的镜像比较简单,也不是什么重要操作,直接把当前机器上的镜像导出压缩包,传到其它机器上再导入就好,参见上文

下面我以国内镜像市场提供商 hub.daocloud.io 为例,演示一下把本地镜像上传到互联网上

# docker pull nginx
# docker images | grep nginx
nginx                               latest              5a8dfb2ca731        22 hours ago        127MB

上面到命令我们从官方仓库下载一个nginx镜像,如果下载速度慢可以配置镜像加速,详情参考上文,下载完毕镜像之后我们可以基于这个镜像利用 Dockerfile 文件呀,动态修改容器里的内容之类的,最后固化成一个新的镜像,看我们实际需求,在这里我们忽略这步

docker tag nginx:latest daocloud.io/test2020/nginx:x1
# docker images | grep daocloud
daocloud.io/test2020/nginx          x1                  5a8dfb2ca731        22 hours ago        127MB

打标签的目的就是标记好,后面知道往哪块上传, test2020 在这里相当于我们在这个网站上创建的账号,也是区分用的

# docker login daocloud.io
# docker push daocloud.io/test2020/nginx:x1

下面介绍2种常用的docker私有仓库服务

docker-registry

https://docs.docker.com/registry/

准备好2台机器
    1台是有公网的,作为docker registry server端
    另1台可以没有公网,但是需要能访问公网,作为client端,把镜像上传到registry server端上去
    2台机器都提前安装好docker,可参见上文

###################################### 搭建registry server端 ####################################################################
在server端进行如下操作,搭建registry server端

docker pull registry:2.7

[root@10-255-20-218 as4k]# docker images | grep "registry"
registry            2.7                 708bc6af7e5e        2 months ago        25.8MB

docker run -d -p 5000:5000 -v /opt/data/registry:/var/lib/registry --restart=always --name registry registry:2.7

[root@10-255-20-218 as4k]# docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
d5983a5da965        registry:2.7        "/entrypoint.sh /etc…"   4 minutes ago       Up 4 minutes        0.0.0.0:5000->5000/tcp   registry

/opt/data/registry 这个目录会自动创建

用 curl 查看仓库中的镜像
curl 127.0.0.1:5000/v2/_catalog
################################################################################################################################

把我们准备好的域名解析到server端的公网IP上去,给域名配置好证书
mkdir -p /as4k/docker-https-cert && cd /as4k/docker-https-cert

#################################################### 配置nginx 证书 转发 ##############################################################

# cat /etc/nginx/conf.d/docker-https.conf
server {
    listen               443 ssl;
    server_name          xtest.as4k.com;

    ssl_certificate      /as4k/docker-https-cert/xtest.as4k.com.pem;
    ssl_certificate_key  /as4k/docker-https-cert/xtest.as4k.com.key;
    ssl_session_timeout  5m;
    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;

    client_max_body_size      0;
    chunked_transfer_encoding on;

    index index.html index.htm index.php;
    location / {
        proxy_pass http://127.0.0.1:5000;
        proxy_set_header Host $host;
        proxy_connect_timeout 3600;
        proxy_send_timeout 3600;
        proxy_read_timeout 3600;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_buffering off;
        proxy_request_buffering off;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto http;
    }
}


nginx -t
systemctl restart nginx
#####################################################################################################################################



#################################################### 在client上传镜像测试 ##############################################################

docker pull nginx:latest
docker image tag nginx:latest xtest.as4k.top/nginx:mynginx

# docker images | grep mynginx
xtest.as4k.top/nginx                              mynginx             e791337790a6        3 days ago          127MB

docker push xtest.as4k.top/nginx:mynginx

用 curl 查看仓库中的镜像
curl 127.0.0.1:5000/v2/_catalog   OR   curl https://xtest.as4k.top:443/v2/_catalog

#####################################################################################################################################

本地信任镜像仓库

Docker默认要求仓库地址必须是https方式提供,否则不能直接推送镜像,我们可以在客户端docker的配置文件里配置信任指定的仓库地址,如下所示

vi /etc/docker/daemon.json
{
  "registry-mirror": [
    "https://hub-mirror.c.163.com"
  ],
  "insecure-registries": [
    "192.168.1.118:5000",
    "192.168.1.119:5000"
  ]
}

推送测试
[root@node5 ~]# docker images | grep mysql
daocloud.io/library/mysql   5.7.26              e9c354083de7        9 months ago        373MB
[root@node5 ~]# docker image tag daocloud.io/library/mysql:5.7.26 192.168.1.118:5000/mysql:5.7.26
[root@node5 ~]# docker images | grep mysql
192.168.1.118:5000/mysql    5.7.26              e9c354083de7        9 months ago        373MB
daocloud.io/library/mysql   5.7.26              e9c354083de7        9 months ago        373MB
[root@node5 ~]# docker push 192.168.1.118:5000/mysql:5.7.26
The push refers to repository [192.168.1.118:5000/mysql]
229aaa48c303: Pushed
b5ef0b1ea71d: Pushed
589aba1a5c0c: Pushed
f9c0afa43f04: Pushed
7e7fffcdabb3: Pushed
77737de99484: Pushed
2f1b41b24201: Pushed
007a7f930352: Pushed
c6926fcee191: Pushed
b78ec9586b34: Pushed
d56055da3352: Pushed
5.7.26: digest: sha256:4dca5d54af3e6a333ba01eafc406720af78734be5f660f38588474ed9eef7b97 size: 2621
[root@node5 ~]# curl 192.168.1.118:5000/v2/_catalog
{"repositories":["mysql"]

这种非https的方法缺点非常明显,不安全暂且不谈,需要给每台机器都增加docker配置文件就非常麻烦,比如某台已经有docker容器在运行,可能不方便修改配置重启docker

使用官方的docker registry管理镜像包,有点是简单方便快速,缺点是比较粗糙,很多常用的功能没有直接的接口,需要自己实现,比如查看仓库里的镜像列表,删除镜像,搜索镜像之类,诸如这样的基本操作可能需要开放一套python脚本才能较为方便的管理,因此在生产环境中常常非使用docker registry作为镜像存储仓库,而比较常用类似nexus3, harbor

nexus 3 待完善

用nexus3 做镜像仓库管理 可以实现一个非常有用的功能 下载镜像如果仓库没有则nexus3会自动从互联网上下载好 然后缓存到nexus3机器上 之后我们再从nexus3机器上下载 就相当于内网下载了

使用 Docker 官方的 Registry 创建的仓库面临一些维护问题。比如某些镜像删除以后空间默认是不会回收的,需要一些命令去回收空间然后重启 Registry 程序。在企业中把内部的一些工具包放入 Nexus 中是比较常见的做法,最新版本 Nexus3.x 全面支持 Docker 的私有镜像。所以使用 Nexus3.x 一个软件来管理 Docker , Maven , Yum , PyPI 等是一个明智的选择。

docker run -d --name nexus3 --restart=always -p 8081:8081 -v /as4k/nexus-data:/nexus-data  sonatype/nexus3


等待 3-5 分钟,如果 nexus3 容器没有异常退出,那么你可以使用浏览器打开 http://YourIP:8081 访问 Nexus 了。
第一次启动 Nexus 的默认帐号是 admin 密码是 admin123 登录以后点击页面上方的齿轮按钮进行设置。

Docker 存储

在容器中管理数据主要有两种方式:

  1. 挂载主机目录 (bind mounts)
  2. 数据卷(volumes)

挂载主机目录

这个直白的理解就是把物理机(宿主机)上的目录或文件,映射给一个指定的容器用,比如 -v /tmp:/tmp ,左边指定的物理机的目录,右边指的是容器里的目录,机制同linux中的mount命令很像,这样映射之后无论是在容器外(宿主机)还是容器里修改/tmp里的文件,改动的都是同一个地方,即宿主机上的/tmp,容器里的/tmp相当于看不见了,容器里原来/tmp里的内容,相当于被隐藏了

$ docker run -d -P \
    --name web \
    # -v /src/webapp:/opt/webapp \
    --mount type=bind,source=/src/webapp,target=/opt/webapp \
    training/webapp \
    python app.py

如果本地目录不存在 Docker 会自动为你创建一个文件夹

Docker 挂载主机目录的默认权限是 读写,用户也可以通过增加 readonly 指定为 只读

$ docker run -d -P \
    --name web \
    # -v /src/webapp:/opt/webapp:ro \
    --mount type=bind,source=/src/webapp,target=/opt/webapp,readonly \
    training/webapp \
    python app.py

查看数据卷的具体信息
    docker inspect web

我在实际工作中遇到的挂载单个文件进入容器里的应用场景

  • 配置文件 比如nginx配置文件 redis配置文件之类
  • 单独的jar包 容器里某个目录,比如 /data/jars 里面有一堆jar包,每次升级这里面的jar包都会更新,但是某个客户的软件是个定制版,需要单独在容器里 /data/jars 增加一个jar包

数据卷

这个功能不太常用,理解起来不直观


docker volume create my-vol
docker volume ls
docker volume inspect my-vol

$ docker run -d -P \
    --name web \
    --mount source=my-vol,target=/webapp \
    training/webapp \
    python app.py

$ docker volume rm my-vol

无主的数据卷可能会占据很多空间,要清理请使用以下命令
    docker volume prune

存储常见疑问

宿主机的目录挂载出来给容器用,该目录如果直接删除重建,容器会报错,需要重启容器

Docker 网络

我们知道正常服务与外部通信用的都是端口,比如起个MySQL监听3306端口,而我们如果起个mysql容器就需要把宿主机上的端口映射给内部使用,比如 -p 4000:3306 ,看起来就像nginx端口转发的功能一样访问外部4000端口就相当于访问内部的3306端口了

如果我们用的是端口映射的方法,则不用分单机还是集群环境,都是直接IP:Port,就能访问到指定服务,但是如果我们的宿主机上的端口不够用,或者我们书写一套启动脚本,里面所有连接MySQL的地方我们都写死用3306端口,但是假如某台机器上3306端口已经被占用了,我们就要一个个修改3306为其它端口,可以看到这种方式对端口资源比较依赖,而如果我们能直接通过容器名(有时候不一定叫容器名)+内部端口,访问指定的服务,就没有这些问题了,我们不需要在外部映射端口,这种需求如果是单机的则用Docker Compose就好,如果是跨机器可以考虑K8S,Docker Swarm,如果我们使用的是公有云上的Dokcer服务,有可能跨机器的2层网路已经被服务商打通

端口映射

docker run -d -P training/webapp python app.py
当使用 -P 标记时,Docker 会随机映射一个 49000~49900 的端口到内部容器开放的网络端口

-p 则可以指定要映射的端口 ip:hostPort:containerPort
docker run -d -p 5000:5000 training/webapp python app.py

Docker 还可以映射一个端口范围,还可以区分UDP和TCP端口,Docker默认监听的都是 0.0.0.0 ,这个也可以改,这些不太常用,用到可去官方文档查

注意我们在这里指定的端口,并不一定就是容器服务内部本身的端口,容器内部具体监听的端口可以使用 docker inspect查看,或者docker exec进入容器内部看

容器内部 DNS

cat /etc/docker/daemon.json
{
  "dns" : [
    "114.114.114.114",
    "8.8.8.8"
  ]
}

单机容器之间通信

Docker Compose

docker run -it --rm --name busybox1 --network my-net busybox sh

集群容器之间通信

Docker Swarm OR Kubernetes

Docker 网络基础原理

当 Docker 启动时,会自动在主机上创建一个 docker0 虚拟网桥,实际上是 Linux 的一个 bridge,可以理解为一个软件交换机。它会在挂载到它的网口之间进行转发。

同时,Docker 随机分配一个本地未占用的私有网段(在 RFC1918 中定义)中的一个地址给 docker0 接口。比如典型的 172.17.42.1,掩码为 255.255.0.0。此后启动的容器内的网口也会自动分配一个同一网段(172.17.0.0/16)的地址。

当创建一个 Docker 容器的时候,同时会创建了一对 veth pair 接口(当数据包发送到一个接口时,另外一个接口也可以收到相同的数据包)。这对接口一端在容器内,即 eth0;另一端在本地并被挂载到 docker0 网桥,名称以 veth 开头(例如 vethAQI2QT)。通过这种方式,主机可以跟容器通信,容器之间也可以相互通信。Docker 就创建了在主机和所有容器之间一个虚拟共享网络。

容器的访问控制,主要通过 Linux 上的 iptables 防火墙来进行管理和实现。iptables 是 Linux 上默认的防火墙软件,在大部分发行版中都自带。

Docker 1.2.0 开始支持在运行中的容器里编辑 /etc/hosts, /etc/hostname 和 /etc/resolv.conf 文件。但是这些修改是临时的,只在运行的容器中保留,容器终止或重启后并不会被保存下来,也不会被 docker commit 提交。

更改 docker0 网桥的默认地址

docker安装完毕后,会自动生成一个网卡名为docker0的网桥,如果其默认分配的网段地址和已有地址段冲突,可按如下步骤修改

查看默认地址段如下
[root@node4 ~]# ifconfig | grep -A2 docker0
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255

##########################################################################################

方法一:修改/usr/lib/systemd/system/docker.service
ExecStart=/usr/bin/dockerd --bip=192.168.10.1/24

systemctl daemon-reload
systemctl restart docker

##########################################################################################

方法二:删除原有配置,重新创建docker0
yum install bridge-utils
brctl show
systemctl stop docker.service
ip link set dev docker0 down
brctl delbr docker0
iptables -t nat -F POSTROUTING
创建新的网桥
brctl addbr docker0
ip addr add 192.168.10.1/24 dev docker0
ip link set dev docker0 up
在docker配置文件中追加参数
# cat /etc/docker/daemon.json
{
"bip": "192.168.10.1/24"
}
启动docker
systemctl start docker.service

##########################################################################################

查看是否更改成功
[root@node4 ~]# ifconfig | grep -A2 docker0
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.10.1  netmask 255.255.255.0  broadcast 192.168.10.255
        inet6 fe80::42:cdff:fe55:1d40  prefixlen 64  scopeid 0x20<link>

更改docker-compose网桥地址

使用单机容器编码工具,docker-compose时,容器之间的通信网络会用到br-xxx网桥,该网桥会在宿主机建立,示例如下:

[root@xingyongsheng ~]# ifconfig | head
br-35fc9d6212bd: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.16.238.1  netmask 255.255.255.0  broadcast 172.16.238.255
        ether 02:42:7a:4f:9a:b9  txqueuelen 0  (Ethernet)
        RX packets 6441897  bytes 8754859570 (8.1 GiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 1525889  bytes 953197479 (909.0 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
该网段也有可能与已有网段重复,若要修改此网段,可按如下步骤进行。安全停掉所有用docker-compose编排的容器,这里建议使用docker-compose down来彻底停掉容器,并自动帮你移除docker-compose之前创建的网桥。之后修改docker-compose.yml文件,增加自定义网络段,如下所示。

version: '2.1'
networks:
  as4k:
    ipam:
      config:
        - subnet: 192.168.10.0/24

services:
  zk1:
    image: ${REGISTRY}/${ZOOKEEPER_IMAGE}
    container_name: zk1
    restart: always
    volumes:
      - ${ZK_DATA_DIR}:/var/lib/zookeeper/data
      - ${ZK_LOG_DIR}:/var/lib/zookeeper/log
    environment:
      - ZOOKEEPER_CLIENT_PORT=2181
      - KAFKA_OPTS=-Xmx500m -Xms500m
    networks:
      - as4k

清空iptables-重置docker

我们知道docker的网络转发实现是需要用到iptables的,docker在重启的时候会自动帮我们配置好相关的iptalbes规则,一般情况下我们都不用关心内部原理,但是如果我们不小心自己添加了一些乱七八糟但iptables规则,导致docker网络功能异常,此时我们当然可以一步步分析具体是哪条iptables规则有问题,不过最快的办法是直接清空全部规则,重启docker

systemctl stop firewalld; systemctl stop docker ;iptables -F && iptables -X && iptables -Z ;iptables -L

systemctl start docker; iptables -L

docker system prune --force; docker image prune --force;  docker container prune --force; docker network prune --force; docker volume prune --force

Docker Compose

Docker Compose 是 Docker 官方编排(Orchestration)项目之一,常用在单机容器之间通过容器名互相通信的时候用

比如我们希望使用Dokcer搭建一个LAMP环境,可以通过一个单独的 docker-compose.yml 模板文件(YAML 格式)来定义一组相关联的应用容器为一个项目

Compose 项目由 Python 编写,实现上调用了 Docker 服务提供的 API 来对容器进行管理。因此,只要所操作的平台支持 Docker API,就可以在其上利用 Compose 来进行编排管理。

安装与卸载

#http://fs.as4k.com:7778/chfs/shared/soft/docker-compose-Linux-x86_64
#http://as4k.top:7000/chfs/shared/linux-pkg/docker-compose-Linux-x86_64
wget https://github.com/docker/compose/releases/download/1.24.1/docker-compose-Linux-x86_64
cp docker-compose-Linux-x86_64 /usr/bin/docker-compose
chmod +x /usr/bin/docker-compose
docker-compose version

部署 wordpress 示例

mkdir -p xwordpress && cd xwordpress
vi docker-compose.yml
version: "3"
services:

   db:
     image: mysql:8.0
     command:
      - --default_authentication_plugin=mysql_native_password
      - --character-set-server=utf8mb4
      - --collation-server=utf8mb4_unicode_ci     
     volumes:
       - db_data:/var/lib/mysql
     restart: always
     environment:
       MYSQL_ROOT_PASSWORD: somewordpress
       MYSQL_DATABASE: wordpress
       MYSQL_USER: wordpress
       MYSQL_PASSWORD: wordpress

   wordpress:
     depends_on:
       - db
     image: wordpress:latest
     ports:
       - "8000:80"
     restart: always
     environment:
       WORDPRESS_DB_HOST: db:3306
       WORDPRESS_DB_USER: wordpress
       WORDPRESS_DB_PASSWORD: wordpress
volumes:
  db_data:

# docker-compose up -d

[root@node4 xwordpress]# docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
f570117ca6ab        wordpress:latest    "docker-entrypoint.s…"   4 seconds ago       Up 2 seconds        0.0.0.0:8000->80/tcp     xwordpress_wordpress_1
42b433136251        mysql:8.0           "docker-entrypoint.s…"   4 seconds ago       Up 3 seconds        3306/tcp, 33060/tcp      xwordpress_db_1
[root@node4 xwordpress]# docker stats --no-stream
CONTAINER ID        NAME                     CPU %               MEM USAGE / LIMIT     MEM %               NET I/O             BLOCK I/O           PIDS
f570117ca6ab        xwordpress_wordpress_1   0.00%               78.63MiB / 3.858GiB   1.99%               2.73MB / 4.68MB     19.4MB / 31.7kB     11
42b433136251        xwordpress_db_1          0.48%               393.9MiB / 3.858GiB   9.97%               777kB / 2.22MB      26.3MB / 766MB      42

上面的内容相信不用过多解释,用的都是hub.docker.com上的官方镜像,mysql数据直接存储在数据卷里了,没映射目录
运行 docker-compose up -d 就会拉取镜像再创建我们所需要的镜像,然后启动 wordpress 和数据库容器。 接着浏览器访问 ip:8000 端口就能看到 WordPress 安装界面了。

使用

docker-compose --help
docker-compose [-f <arg>...] [options] [COMMAND] [ARGS...]
有些先后顺序注意下,先docker-compose命令的参数,在 子命令 子命令参数

启动全部服务
docker-compose up --help
docker-compose up
    -f, --file FILE             Specify an alternate compose file (default: docker-compose.yml)
    -d, --detach                Detached mode: Run containers in the background

停掉全部服务
docker-compose down
    -f, --file FILE             Specify an alternate compose file (default: docker-compose.yml)

启动单个服务
docker-compose -f FILE.yml up -d SERVICE
    -f, --file FILE             Specify an alternate compose file (default: docker-compose.yml)
    -d, --detach                Detached mode: Run containers in the background


停掉单个服务
docker-compose -f FILE.yml rm -fs SERVICE
    -f, --force   Don't ask to confirm removal
    -s, --stop    Stop the containers, if required, before removing
    -v            Remove any anonymous volumes attached to containers

查看服务名称
egrep -v "^$|^#" docker-compose.yml    OR
docker-compose  -f docker-compose.yml  config --services

关于启停顺序

我们知道很多服务的组合都有启动顺序的要求,比如启动wordpress之前要先确保MySQL服务已经正常启动,此时如果直接启动全部服务则无法保证顺序,可以利用Docker-Compose的depend功能来确保顺序,不过更直观的操作,是直接书写脚本,自己控制启停顺序

更改docker-compose网桥地址

使用单机容器编码工具,docker-compose时,容器之间的通信网络会用到br-xxx网桥,该网桥会在宿主机建立,示例如下:

该网段也有可能与已有网段重复,若要修改此网段,可按如下步骤进行。安全停掉所有用docker-compose编排的容器,这里建议使用docker-compose down来彻底停掉容器,并自动帮你移除docker-compose之前创建的网桥。之后修改docker-compose.yml文件,增加自定义网络段,如下所示。

cat docker-compose.yml
version: '2.1'
networks:
  xpd:
    ipam:
      config:
        - subnet: 192.168.10.0/24

services:
  zk1:
    image: ${REGISTRY}/${ZOOKEEPER_IMAGE}
    container_name: zk1
    restart: always
    volumes:
      - ${ZK_DATA_DIR}:/var/lib/zookeeper/data
      - ${ZK_LOG_DIR}:/var/lib/zookeeper/log
    environment:
      - ZOOKEEPER_CLIENT_PORT=2181
      - KAFKA_OPTS=-Xmx500m -Xms500m
    networks:
      - xpd

在 docker-compose 中使用变量

https://docs.docker.com/compose/environment-variables/
https://docs.docker.com/compose/env-file/

参考链接

https://github.com/docker/compose
https://github.com/docker/docker.github.io/blob/master/compose/compose-file/compose-versioning.md
https://docs.docker.com/compose/

Compose file version 3 reference
https://docs.docker.com/compose/compose-file/

Compose file version 2 reference
https://docs.docker.com/compose/compose-file/compose-file-v2/

Compose file version 1 reference
https://docs.docker.com/compose/compose-file/compose-file-v1/

Docker 搭建 LNMP 环境

待更新

https://github.com/khs1994-docker/lnmp

https://docs.lnmp.khs1994.com/why.html#%E5%89%8D%E8%A8%80

Docker Swarm

https://docs.docker.com/engine/swarm/swarm-tutorial/

Swarm mode 内置 kv 存储功能,提供了众多的新特性,比如:具有容错能力的去中心化设计、内置服务发现、负载均衡、路由网格、动态伸缩、滚动更新、安全传输等。但是该功能已逐步被Kubernetes替换,不被官方支持

我们在生成环境中使用的时候也发现,在一些网络较为恶劣的环境,或者底层虚拟化环境使用的VMware的情况下,Docker Swarm很容易有各种莫名其妙的网络问题,建议不要在生产环境中使用Docker Swarm

Docker 安全

评估 Docker 的安全性时,主要考虑三个方面:

由内核的命名空间和控制组机制提供的容器内在安全
Docker 程序(特别是服务端)本身的抗攻击性
内核安全性的加强机制对容器安全性的影响

运行一个容器或应用程序的核心是通过 Docker 服务端。Docker 服务的运行目前需要 root 权限,因此其安全性十分关键。

首先,确保只有可信的用户才可以访问 Docker 服务。Docker 允许用户在主机和容器间共享文件夹,同时不需要限制容器的访问权限,这就容易让容器突破资源限制。例如,恶意用户启动容器的时候将主机的根目录 / 映射到容器的 /host 目录中,那么容器理论上就可以对主机的文件系统进行任意修改了。这听起来很疯狂?但是事实上几乎所有虚拟化系统都允许类似的资源共享,而没法禁止用户共享主机根文件系统到虚拟机系统。

Docker 底层实现

Docker 底层的核心技术包括 Linux 上的命名空间(Namespaces)、控制组(Control groups)、Union 文件系统(Union file systems)和容器格式(Container format)。

Docker 采用了 C/S 架构,包括客户端和服务端。Docker 守护进程 (Daemon)作为服务端接受来自客户端的请求,并处理这些请求(创建、运行、分发容器)。

客户端和服务端既可以运行在一个机器上,也可通过 socket 或者 RESTful API 来进行通信。

联合文件系统(UnionFS)是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite several directories into a single virtual filesystem)。

联合文件系统是 Docker 镜像的基础。镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。

Docker 目前支持的联合文件系统包括 OverlayFS, AUFS, Btrfs, VFS, ZFS 和 Device Mapper。

各 Linux 发行版 Docker 推荐使用的存储驱动如下表。

Linux 发行版	        Docker 推荐使用的存储驱动
Docker CE on Ubuntu	   overlay2 (16.04 +)
Docker CE on Debian	   overlay2 (Debian Stretch), aufs, devicemapper
Docker CE on CentOS	   overlay2
Docker CE on Fedora	   overlay2

在可能的情况下,推荐 使用 overlay2 存储驱动,overlay2 是目前 Docker 默认的存储驱动,以前则是 aufs。你可以通过配置来使用以上提到的其他类型的存储驱动。

最初,Docker 采用了 LXC 中的容器格式。从 0.7 版本以后开始去除 LXC,转而使用自行开发的 libcontainer,从 1.11 开始,则进一步演进为使用 runC 和 containerd。

Docker 的网络实现其实就是利用了 Linux 上的网络命名空间和虚拟网络设备(特别是 veth pair)。建议先熟悉了解这两部分的基本概念再阅读本章。

部署常用容器

官方镜像体积都比较小,只带有一些基本的组件。 精简的系统有利于安全、稳定和高效的运行,也适合进行个性化定制。

出于安全考虑,几乎所有官方制作的镜像都没有安装 SSH 服务,无法通过用户名和密码直接登录到容器中。

busybox

https://hub.docker.com/_/busybox
https://busybox.net/

What is BusyBox? The Swiss Army Knife of Embedded Linux
Coming in somewhere between 1 and 5 Mb in on-disk size (depending on the variant), BusyBox is a very good ingredient to craft space-efficient distributions.

BusyBox 是一个集成了一百多个最常用 Linux 命令和工具(如 cat、echo、grep、mount、telnet 等)的精简工具箱,它只需要几 MB 的大小,很方便进行各种快速验证,被誉为“Linux 系统的瑞士军刀”。

docker run -it --rm busybox:1.31

[root@10-255-20-218 ~]# docker images
REPOSITORY                          TAG                 IMAGE ID            CREATED             SIZE
busybox                             1.31                be5888e67be6        7 days ago          1.22MB

docker pull busybox:1.31

https://busybox.net/FAQ.html
The "busybox-i686" version should run on both 32-bit and 64-bit x86 PCs

二进制直接使用  应对很多容器内置的命令太少问题
wget https://busybox.net/downloads/binaries/1.31.0-defconfig-multiarch-musl/busybox-x86_64

wget http://fs.as4k.com:7778/chfs/shared/soft/busybox-x86_64
OR http://as4k.top:7000/chfs/shared/linux-pkg/busybox
chmod +x busybox-x86_64
./busybox-x86_64 netstat -lntup
./busybox-x86_64 ls --help

[root@dp-file-share soft]# ./busybox-x86_64  --list | wc -l
396

贼好用 内置300个常用命令,大小只有1M,二进制跨全平台,32位,64位全支持,用在容器内部就更爽了

alpine

https://www.alpinelinux.org/

Alpine 操作系统是一个面向安全的轻型 Linux 发行版。它不同于通常 Linux 发行版,Alpine 采用了 musl libc 和 busybox 以减小系统的体积和运行时资源消耗,但功能上比 busybox 又完善的多,因此得到开源社区越来越多的青睐。在保持瘦身的同时,Alpine 还提供了自己的包管理工具 apk,可以通过 https://pkgs.alpinelinux.org/packages 网站上查询包信息,也可以直接通过 apk 命令直接查询和安装各种软件。

Alpine Docker 镜像也继承了 Alpine Linux 发行版的这些优势。相比于其他 Docker 镜像,它的容量非常小,仅仅只有 5 MB 左右(对比 Ubuntu 系列镜像接近 200 MB),且拥有非常友好的包管理机制。官方镜像来自 docker-alpine 项目。

$ docker run alpine echo '123'

该镜像还是少了很多内容的,比如alpine的tomcat不带有jdk,只有jre,如果不在乎磁盘空间,直接使用centos,ubuntu就好

Debian

众多的 Linux 发行版,例如 Ubuntu、Knoppix 和 Linspire 及 Xandros 等,都基于 Debian GNU/Linux。

$ docker run -it debian bash

https://www.debian.org/releases/

Debian 10 (buster) — current stable release
Debian 9 (stretch) — oldstable release
Debian 8 (jessie) — oldoldstable release
Debian 7 (wheezy) — obsolete stable release
Debian 6.0 (squeeze) — obsolete stable release
Debian GNU/Linux 5.0 (lenny) — obsolete stable release
Debian GNU/Linux 4.0 (etch) — obsolete stable release
Debian GNU/Linux 3.1 (sarge) — obsolete stable release
Debian GNU/Linux 3.0 (woody) — obsolete stable release
Debian GNU/Linux 2.2 (potato) — obsolete stable release
Debian GNU/Linux 2.1 (slink) — obsolete stable release
Debian GNU/Linux 2.0 (hamm) — obsolete stable release

Ubuntu

Ubuntu 基于 Debian 发行版和 GNOME/Unity 桌面环境,与 Debian 的不同在于它每 6 个月会发布一个新版本,每 2 年推出一个长期支持 (Long Term Support,LTS) 版本,一般支持 3 年时间。

$ docker run -ti ubuntu:18.04 /bin/bash
root@7d93de07bf76:/# cat /etc/os-release

当试图直接使用 apt-get 安装一个软件的时候,会提示 E: Unable to locate package。
这并非系统不支持 apt-get 命令。Docker 镜像在制作时为了精简清除了 apt 仓库信息,因此需要先执行 apt-get update 命令来更新仓库信息。更新信息后即可成功通过 apt-get 命令来安装软件。

apt-get install curl

Ubuntu 18.04.4 LTS (Bionic Beaver)
https://ubuntu.com/

CentOS

CentOS 和 Fedora 都是基于 Redhat 的常见 Linux 分支。CentOS 是目前企业级服务器的常用操作系统;Fedora 则主要面向个人桌面用户。

CentOS(Community Enterprise Operating System,中文意思是:社区企业操作系统),它是基于 Red Hat Enterprise Linux 源代码编译而成。由于 CentOS 与 Redhat Linux 源于相同的代码基础,所以很多成本敏感且需要高稳定性的公司就使用 CentOS 来替代商业版 Red Hat Enterprise Linux。CentOS 自身不包含闭源软件。

$ docker run -it centos bash
docker pull centos:7.8.2003
docker pull daocloud.io/library/centos:7.8.2003

docker run --name centos78 -d daocloud.io/library/centos:7.8.2003 tail -F /tmp/tmp.txt

https://www.centos.org
https://hub.docker.com/_/centos/
https://github.com/CentOS/CentOS-Dockerfiles


#################################### 启用systemd ###################################
cat << 'EOF' > Dockerfile
FROM centos:7.8.2003
ENV container docker
RUN (cd /lib/systemd/system/sysinit.target.wants/; for i in *; do [ $i == \
systemd-tmpfiles-setup.service ] || rm -f $i; done); \
rm -f /lib/systemd/system/multi-user.target.wants/*;\
rm -f /etc/systemd/system/*.wants/*;\
rm -f /lib/systemd/system/local-fs.target.wants/*; \
rm -f /lib/systemd/system/sockets.target.wants/*udev*; \
rm -f /lib/systemd/system/sockets.target.wants/*initctl*; \
rm -f /lib/systemd/system/basic.target.wants/*;\
rm -f /lib/systemd/system/anaconda.target.wants/*;
VOLUME [ "/sys/fs/cgroup" ]
CMD ["/usr/sbin/init"]
EOF

docker build -t local/c7-systemd .

docker run -it -v /sys/fs/cgroup:/sys/fs/cgroup:ro -p 80:80 local/c7-systemd-httpd

MySQL

dir=/home/mysql-data; mkdir -p $dir; docker run --restart always --name mysql-source -d -v $dir:/var/lib/mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 daocloud.io/library/mysql:5.7.26

############################ 脚本化 ################################################

#!/bin/bash
#filename: mysql-docker.sh

CNAME=mysql
DIR=/home/mysql-data

start() {
docker run --name $CNAME -d \
-v $DIR:/var/lib/mysql \
-v /etc/my.cnf:/etc/my.cnf \
-v /root/busybox-x86_64:/root/busybox-x86_64 \
--add-host dp-thrall-mysql1:192.168.1.52 \
--add-host dp-thrall-mysql2:192.168.1.45 \
--add-host dp-thrall-mysql3:192.168.1.54 \
-p 3307:3307 \
-p 33062:33062 \
-e MYSQL_ROOT_PASSWORD=123456 daocloud.io/library/mysql:5.7.26
}

stop() {
docker rm -fv $CNAME
}

clear() {
stop
rm -rf $DIR
}

restart() {
stop
sleep 1
start    
}

$1

########################################################################################

nginx

https://hub.daocloud.io/repos/2b7310fb-1a50-48f2-9586-44622a2d1771
docker pull daocloud.io/library/nginx:1.16

docker run --name some-nginx -v /some/content:/usr/share/nginx/html:ro -d daocloud.io/library/nginx:1.16


as4k@bogon view-xdocs % cat docker-compose.yml
version: "3"
services:
   nginx-hugo-xdocs:
     image: daocloud.io/library/nginx:1.16
     container_name: nginx-hugo-xdocs
     ports:
       - "80:80"
       - "8001:8001"
     volumes:
       - /Users/as4k/As4k/xdocs/public:/Users/as4k/As4k/xdocs/public
       - /Users/as4k/As4k/xdocs/image:/Users/as4k/As4k/xdocs/image
       - /Users/as4k/As4k/xdocker/view-xdocs/nginx-conf:/etc/nginx/conf.d
       - /Users/as4k/As4k/xdocker/busybox:/usr/local/bin/busybox

python

daocloud.io/library/python:3.6.2

docker run --name xdj -p 8000:8000 -v "$PWD":/usr/src/myapp -w /usr/src/myapp daocloud.io/library/python:3.6.2 tail -F /tmp/tmp.txt

ssh

https://www.cnblogs.com/ruanqj/p/7374544.html

docker run --name centos78 -d daocloud.io/library/centos:7.8.2003 tail -F /tmp/tmp.txt
yum install passwd openssl openssh openssh-server openssh-clients -y


mkdir -p /var/run/ssh
ssh-keygen -q -t rsa -b 2048 -f /etc/ssh/ssh_host_rsa_key -N ''
ssh-keygen -q -t ecdsa -f /etc/ssh/ssh_host_ecdsa_key -N ''
ssh-keygen -t dsa -f /etc/ssh/ssh_host_ed25519_key -N ''

sed -i "s/#UsePrivilegeSeparation.*/UsePrivilegeSeparation no/g" /etc/ssh/sshd_config
sed -i "s/UsePAM.*/UsePAM no/g" /etc/ssh/sshd_config

/usr/sbin/sshd -D

echo 123456 | passwd --stdin root

ssh root@localhost

nexus 离线安装 docker

cat << 'EOF' > /etc/yum.repos.d/docker-ce.repo
[docker-ce-stable]
name=Docker CE Stable - $basearch
#baseurl=https://download.docker.com/linux/centos/7/$basearch/stable
baseurl=http://192.168.1.112:8081/repository/docker-ce/$basearch/stable
enabled=1
gpgcheck=0
#gpgkey=https://download.docker.com/linux/centos/gpg
EOF


yum list docker-ce --showduplicates | sort -r

yum install docker-ce-18.09.9 docker-ce-cli-18.09.9 containerd.io
OR  yum install docker-ce-<VERSION_STRING> docker-ce-cli-<VERSION_STRING> containerd.io
OR  yum install docker-ce docker-ce-cli containerd.io

systemctl start docker
systemctl status docker
systemctl enable docker

覆盖 entrypoint

docker run -d -it --entrypoint "/bin/bash" xxx:v10

常见问题

本地的镜像文件都存放在哪里?
答:与 Docker 相关的本地资源默认存放在 /var/lib/docker/ 目录下,以 overlay2 文件系统为例,其中 containers 目录存放容器信息,image 目录存放镜像信息,overlay2 目录下存放具体的镜像层文件。

如何获取某个容器的 PID 信息?
答:docker inspect --format '{{ .State.Pid }}' <CONTAINER ID or NAME>

如何获取某个容器的 IP 地址?
答:docker inspect --format '{{ .NetworkSettings.IPAddress }}' <CONTAINER ID or NAME>

如何给容器指定一个固定 IP 地址,而不是每次重启容器 IP 地址都会变?
答:使用以下命令启动容器可以使容器 IP 固定不变
$ docker network create -d bridge --subnet 172.25.0.0/16 my-net
$ docker run --network=my-net --ip=172.25.3.3 -itd --name=my-container busybox

可以在一个容器中同时运行多个应用进程么?
答:一般并不推荐在同一个容器内运行多个应用进程。如果有类似需求,可以通过一些额外的进程管理机制,比如 supervisord 来管理所运行的进程。可以参考 https://docs.docker.com/config/containers/multi-service_container/ 。

如何控制容器占用系统资源(CPU、内存)的份额?
答:在使用 docker create 命令创建容器或使用 docker run 创建并启动容器的时候,可以使用 -c|--cpu-shares[=0] 参数来调整容器使用 CPU 的权重;使用 -m|--memory[=MEMORY] 参数来调整容器使用内存的大小。

如何更改 Docker 的默认存储位置?
答:修改配置文件 /etc/docker/daemon.json 的 "data-root" 项

开发环境中 Docker 和 Vagrant 该如何选择?
答:Docker 不是虚拟机,而是进程隔离,对于资源的消耗很少,但是目前需要 Linux 环境支持。Vagrant 是虚拟机上做的封装,虚拟机本身会消耗资源。
如果本地使用的 Linux 环境,推荐都使用 Docker。
如果本地使用的是 macOS 或者 Windows 环境,那就需要开虚拟机,单一开发环境下 Vagrant 更简单;多环境开发下推荐在 Vagrant 里面再使用 Docker 进行环境隔离。

参考资料

https://docker_practice.gitee.io/
https://github.com/yeasy/docker_practice

离线阅读
docker pull dockerpracticesig/docker_practice && docker run -it --rm -p 4000:80 dockerpracticesig/docker_practice

遗留问题
非root用户如何使用docker ?