Docker中午学习网站

1.概念

1.镜像(Image)

  • 相当于root文件系统(必需:程序、库、资源、配置、运行时:匿名卷、环境变量、用户等)
  • 镜像不包含任何动态数据,其内容在构建之后也不会被改变。

    1.分层存储

  • 利用Union FS 的技术,设计为分层存储的架构。
  • 一组文件系统或多层文件系统联合组成。
  • 构建时,会一层层构建,前一层是后一层的基础。
  • 每一层构建完就不会再发生改变,后一层上的任何改变之后发生再自己这一层(删除前一层文件的操作,实际不是真的删除前一层的文件,而是仅在当前层标记为该文件已删除,文件看不到,但是该文件会一直跟随镜像)。
  • 每一层尽量只包含该层需要添加的东西,任何额外的东西应该在该层构建结束前清理掉。

2.容器(Container)

  • 容器的实质是进程
  • 容器进程运行属于自己的独立的命名空间。因此容器可以拥有自己的root文件系统、网络配置、进程空间、独立的用户id空间。
  • 容器同样也是分层存储,镜像为基础层,在其上面创建一个当前容器的存储层(容器存储层)
  • 容器存储层和容器的生命周期一致,容器消亡时,容器存储层也消亡,所以不可以进行存储。
  • 容器不应该向其存储层写入任何数据,数据存储层要保持无状态化。
  • 所有的文件写入都应该使用数据卷(Volume)或者绑定宿主目录。容器删除数据不会消失。

3.仓库(Repository)

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

私有仓库

  • Docker Registry

2.安装 Docker

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

1.Debian

  • 1.更新包列表

    1
    sudo apt update
  • 2.安装必备包

    1
    sudo apt install apt-transport-https ca-certificates curl gnupg2 software-properties-common
  • 3.将官方Docker存储库的GPG密钥添加到系统

    1
    curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -
  • 4.将Docker存储库添加到APT源

    1
    sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/debian $(lsb_release -cs) stable"
  • 5.确保要从Docker repo而不是默认的Debian repo安装

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    apt-cache policy docker-ce

    #

    docker-ce:
    Installed: (none)
    Candidate: 18.06.1~ce~3-0~debian
    Version table:
    18.06.1~ce~3-0~debian 500
    500 https://download.docker.com/linux/debian stretch/stable amd64 Packages

    请注意,未安装docker-ce ,但安装的候选者来自Debian 9的Docker存储库( stretch )
  • 6.安装Docker

    1
    sudo apt install docker-ce
  • 7.守护进程&开机自启&启动

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 查看状态
    sudo systemctl status docker

    # 开机自启动
    sudo systemctl enable docker

    # 启动docker
    sudo systemctl start docker

    # 关闭docker
    sudo systemctl stop docker

2.CentOS

  • 1.卸载旧版

    1
    2
    3
    4
    5
    6
    7
    8
    sudo yum remove docker \
    docker-client \
    docker-client-latest \
    docker-common \
    docker-latest \
    docker-latest-logrotate \
    docker-logrotate \
    docker-engine
  • 2.安装Docker Engine-Community。新主机上首次安装Docker Engine-Community之前,需要设置Docker仓库。之后可以从仓库更新和安装Docker。安装所需软件包。

    1
    2
    3
    sudo yum install -y yum-utils \
    device-mapper-persistent-data \
    lvm2
  • 3.设置稳定仓库

    1
    2
    3
    sudo yum-config-manager \
    --add-repo \
    https://download.docker.com/linux/centos/docker-ce.repo
  • 4.安装Docker Engine-Community和containerd(默认最新)

    1
    sudo yum install docker-ce docker-ce-cli containerd.io -y
  • 5.守护进程&开机自启&启动

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 查看状态
    sudo systemctl status docker

    # 开机自启动
    sudo systemctl enable docker

    # 启动docker
    sudo systemctl start docker

    # 关闭docker
    sudo systemctl stop docker

    3.开启实验特性

    1.开启Docker CLI的实验特性

  • 1.编辑 [~/.docker/config.json]

    1
    2
    3
    {
    "experimental": "enabled"
    }
  • 2.设置环境变量

    1
    export DOCKER_CLI_EXPERIMENTAL=enabled

    2.开启Dockerd的实验特性

  • 编辑 /etc/docker/daemon.json

    1
    2
    3
    {
    "experimental": true
    }

    3.使用镜像

    1.获取镜像

  • 拉取镜像格式

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

    # 例如
    docker pull ubuntu:18.04
  • Docker 镜像仓库地址:地址的格式一般是 <域名/IP>[:端口号]。默认地址是 Docker Hub。

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

2.列出镜像

  • 列出
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    # 仓库名 标签 镜像ID 创建时间 所占用的空间
    # 镜像id为唯一标识
    docker image ls


    REPOSITORY TAG IMAGE ID CREATED SIZE
    redis latest 5f515359c7f8 5 days ago 183 MB
    nginx latest 05a60462f8ba 5 days ago 181 MB
    mongo 3.2 fe9198c04d62 5 days ago 342 MB
    <none> <none> 00285df0df87 5 days ago 342 MB
    ubuntu 18.04 f753707788c5 4 weeks ago 127 MB
    ubuntu latest f753707788c5 4 weeks ago 127 MB

  • 查看镜像、容器、数据卷所占用的空间
    1
    2
    3
    4
    5
    6
    7
    docker system df

    TYPE TOTAL ACTIVE SIZE RECLAIMABLE
    Images 24 0 1.992GB 1.992GB (100%)
    Containers 1 0 62.82MB 62.82MB (100%)
    Local Volumes 9 0 652.2MB 652.2MB (100%)
    Build Cache

    镜像

  • 构建失败、镜像更新都会导致,可随意删除
    1
    docker image prune

中间层镜像

  • 为了加速镜像构建、重复利用资源,Docker 会利用 中间层镜像。

  • 无标签的镜像很多都是中间层镜像,是其它镜像所依赖的镜像。

  • 无标签镜像不应该删除,否则会导致上层镜像因为依赖丢失而出错。

  • 这些镜像也没必要删除,因为之前说过,相同的层只会存一遍,而这些镜像是别的镜像的依赖,因此并不会因为它们被列出来而多存了一份,无论如何你也会需要它们。

  • 只要删除那些依赖它们的镜像后,这些依赖的中间层镜像也会被连带删除。

    1
    2
    # 查看
    docker image ls -a

    列出部分镜像

  • 根据仓库名列出镜像

    1
    docker image ls ubuntu
  • 根据仓库名和标签

    1
    docker image ls ubuntu:18.04
  • 查看mongo:3.2 之后建立的镜像

    1
    docker image ls -f since=mongo:3.2
  • 查看mongo:3.2 之前建立的镜像

    1
    docker image ls -f before=mongo:3.2
  • 通过label查找

    1
    docker image ls -f label=com.example.version=0.1

    以特定格式显示

  • 列出image id

    1
    docker image ls -q
  • 通过Go的模版语法查询

    1
    2
    3
    4
    5
    6
    7
    8
    9
    docker image ls --format "{{.ID}}: {{.Repository}}"

    5f515359c7f8: redis
    05a60462f8ba: nginx
    fe9198c04d62: mongo
    00285df0df87: <none>
    f753707788c5: ubuntu
    f753707788c5: ubuntu
    1e0c3dd64ccd: ubuntu
  • 表格等距显示

    1
    2
    3
    4
    5
    6
    7
    8
    9
    docker image ls --format "table {{.ID}}\t{{.Repository}}\t{{.Tag}}"

    IMAGE ID REPOSITORY TAG
    5f515359c7f8 redis latest
    05a60462f8ba nginx latest
    fe9198c04d62 mongo 3.2
    00285df0df87 <none> <none>
    f753707788c5 ubuntu 18.04
    f753707788c5 ubuntu latest

2.Dockerfile

1.COPY 复制文件

  • 根据上下文目录copy文件或者文件夹

    1.格式

    1
    2
    COPY [--chown=<user>:<group>] <源路径>... <目标路径>
    COPY [--chown=<user>:<group>] ["<源路径1>",..."<目标路径>"]
  • 源路径可以有多个,也可以是通配符
  • 通配符需要满足GoLang的filepath.Match规则
    1
    2
    COPY index* /mydir/
    COPY inde?.txt /mydir/

    2.其余注意事项

  • <目标路径>可以是容器内的绝对路径,也可以是相对于工作目录的相对路径(WORKDIR指定的路径)。
  • 目标目录不需要事先创建,如果目录不存在会在复制文件前创建相应的目录。
  • COPY指令,源文件的各种元数据都会保留。读、写、执行权限、文件变更时间等

3.设置用户以及用户组

1
2
3
4
COPY --chown=55:mygroup files* /mydir/
COPY --chown=bin files* /mydir/
COPY --chown=1 files* /mydir/
COPY --chown=10:11 files* /mydir/

2.ADD 更高级的复制文件

  • 与COPY功能基本相同
  • <源路径>可以是一个URL,这种情况下,Docker引擎会试图去下载这个链接的文件放到<目标路径>。
  • 下载后的文件权限自动设置为600.
  • 如果下载的是个压缩包,需要解压缩,还是需要RUN命令来进行解压缩,但是如果<源路径>为一个tar压缩文件,压缩格式为gzip、bzip2、xz的情况下,ADD指令会自动解压缩这个压缩文件到<目标路径>去。
  • 尽可能使用COPY指令,COPY语义明确,只有拷贝文件和文件夹的功能。
  • ADD指令会令镜像构建缓存失效,从而可能会令镜像构建变的比较缓慢。
  • 改变文件的所属用户以及所属组
    1
    2
    3
    4
    ADD --chown=55:mygroup files* /mydir/
    ADD --chown=bin files* /mydir/
    ADD --chown=1 files* /mydir/
    ADD --chown=10:11 files* /mydir/

3.CMD容器启动指令

  • Docker不是虚拟机,容器中的主应用应该在前台运行,容器中没有后台服务的概念。
  • 容器即进程

1.格式

  • shell格式

    1
    CMD <命令>
  • exec 格式

    1
    CMD ["可执行文件","参数1","参数2"...]
  • Nginx 示例

    1
    CMD ["nginx", "-g", "daemon off;"]

4.ENTRYPOINT 入口点

  • ENTRYPOINT的格式和RUN指令格式一样,分为exec格式和shell格式。
  • ENTRYPOINT的目的和CMD一样,都是指定容器启动程序以及参数
  • ENTRYPOINT在运行时也可以替代,不过比CMD要略显繁琐,需要dokcer run的参数–entrypoint指定。
  • 当指定了ENTRYPOINT后,CMD的含义就发生了改变,不再是直接的运行其命令,而是将CMD的内容作为参数传给ENTRYPOINT指令,换句话说实际执行时,将变为
    1
    <ENTRYPOINT> "<CMD>"

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

  • 假设我们需要一个得知自己当前公网 IP 的镜像,那么可以先用 CMD 来实现:
    1
    2
    3
    4
    5
    FROM ubuntu:18.04
    RUN apt-get update \
    && apt-get install -y curl \
    && rm -rf /var/lib/apt/lists/*
    CMD [ "curl", "-s", "https://ip.cn" ]
  • 假如我们使用 docker build -t myip . 来构建镜像的话,如果我们需要查询当前公网 IP,只需要执行:
1
2
$ docker run myip
当前 IP:xx.xxx.226.xx 来自:北京市 联通
  • 如果我们希望增加一个参数呢,比如上面的curl命令显示HTTP头信息,就需要加上 -i参数。那么我们可以很直接加-i参数给docker run myip吗

    1
    2
    $ docker run myip -i
    docker: Error response from daemon: invalid header field value "oci runtime error: container_linux.go:247: starting container process caused \"exec: \\\"-i\\\": executable file not found in $PATH\"\n".
  • 我们可以看到可执行文件找不到的报错,executable file not found。之前我们说过,跟在镜像名后面的是 command,运行时会替换 CMD 的默认值。因此这里的 -i 替换了原来的 CMD,而不是添加在原来的 curl -s https://ip.cn 后面。而 -i 根本不是命令,所以自然找不到。

  • 那么如果我们希望加入 -i 这参数,我们就必须重新完整的输入这个命令

    1
    docker run myip curl -s https://ip.cn -i
  • 这显然不是很好的解决方案,而使用 ENTRYPOINT 就可以解决这个问题。现在我们重新用 ENTRYPOINT 来实现这个镜像:

    1
    2
    3
    4
    5
    FROM ubuntu:18.04
    RUN apt-get update \
    && apt-get install -y curl \
    && rm -rf /var/lib/apt/lists/*
    ENTRYPOINT [ "curl", "-s", "https://ip.cn" ]
  • 这次我们再来尝试直接使用 docker run myip -i:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    $ docker run myip -i
    HTTP/1.1 200 OK
    Server: nginx/1.8.0
    Date: Tue, 22 Nov 2016 05:12:40 GMT
    Content-Type: text/html; charset=UTF-8
    Vary: Accept-Encoding
    X-Powered-By: PHP/5.6.24-1~dotdeb+7.1
    X-Cache: MISS from cache-2
    X-Cache-Lookup: MISS from cache-2:80
    X-Cache: MISS from proxy-2_6
    Transfer-Encoding: chunked
    Via: 1.1 cache-2:80, 1.1 proxy-2_6:8006
    Connection: keep-alive

    当前 IP:xx.xxx.226.xx 来自:北京市 联通

    可以看到,这次成功了。这是因为当存在 ENTRYPOINT 后,CMD 的内容将会作为参数传给 ENTRYPOINT,而这里 -i 就是新的 CMD,因此会作为参数传给 curl,从而达到了我们预期的效果

2.场景二:应用运行前的准备工作

  • 启动容器就是启动主进程,但有些时候,启动主进程前,需要一些准备工作。

  • 比如 mysql 类的数据库,可能需要一些数据库配置、初始化的工作,这些工作要在最终的 mysql 服务器运行之前解决。

  • 此外,可能希望避免使用 root 用户去启动服务,从而提高安全性,而在启动服务前还需要以 root 身份执行一些必要的准备工作,最后切换到服务用户身份启动服务。或者除了服务外,其它命令依旧可以使用 root 身份执行,方便调试等。

  • 这些准备工作是和容器 CMD 无关的,无论 CMD 为什么,都需要事先进行一个预处理的工作。这种情况下,可以写一个脚本,然后放入 ENTRYPOINT 中去执行,而这个脚本会将接到的参数(也就是 )作为命令,在脚本最后执行。比如官方镜像 redis 中就是这么做的:

    1
    2
    3
    4
    5
    6
    7
    8
    FROM alpine:3.4
    ...
    RUN addgroup -S redis && adduser -S -G redis redis
    ...
    ENTRYPOINT ["docker-entrypoint.sh"]

    EXPOSE 6379
    CMD [ "redis-server" ]
  • 可以看到其中为了 redis 服务创建了 redis 用户,并在最后指定了 ENTRYPOINT 为 docker-entrypoint.sh 脚本。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #!/bin/sh
    ...
    # allow the container to be started with `--user`
    if [ "$1" = 'redis-server' -a "$(id -u)" = '0' ]; then
    chown -R redis .
    exec su-exec redis "$0" "$@"
    fi

    exec "$@"
  • 该脚本的内容就是根据 CMD 的内容来判断,如果是 redis-server 的话,则切换到 redis 用户身份启动服务器,否则依旧使用 root 身份执行。比如:

    1
    2
    $ docker run -it redis id
    uid=0(root) gid=0(root) groups=0(root)

5.ENV

  • ENV代表环境变量,无论后面什么命令,如RUN,还是运行时的应用,都可以直接使用这里定义的环境变量。

    1.格式

    1
    2
    ENV <key> <value>
    ENV <key1>=<value1> <key2>=<value2>...

    2.使用

  • 也可以在CMD运行脚本的时候在脚本中使用
    1
    2
    3
    4
    5
    6
    # Dockerfile
    ENV NODE_VERSION 10.1.0
    RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz" \

    # CMD脚本使用
    echo $PUBLISH_HOST
  • 启动时使用
    1
    docker build --build-arg PUBLISH_HOST=aValue

6.ARG 构建参数

  • 格式

    1
    ARG <参数名>[=<默认值>]
  • 示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 启动时使用--build-arg覆盖
    # Dockerfile
    ARG PUBLISH_HOST=default # 可设置默认值
    ENV PUBLISH_HOST $PUBLISH_HOST # 保证传递参数是可以被ARG的覆盖

    # 启动命令
    docker build --build-arg PUBLISH_HOST=aValue

    # 脚本中正常获取即可
    echo $PUBLISH_HOST

7.VOLUME 定义匿名卷

  • 格式

    1
    2
    VOLUME ["<路径>","<路径2>"...]
    VOLUME <路径>
  • 容器运行时应该尽量避免发生写入操作,对于数据库雷需要保存的数据,应保存到卷(volume)中

  • 示例

    1
    2
    3
    # -v 指定匿名卷地址

    docker run -d -v mydata:/data xxxx

8.EXPOSE声明端口

  • EXPOSE指令是声明运行时容器提供服务端口,这只是一个声明,在运行时并不会因为这个声明应用就会开启这个端口的服务。

  • Dockerfile中写入这样的声明有两个好处,一个是帮助镜像使用者理解这个镜像服务的守护端口,以便配置映射。

  • 另一个用处这是在运行实用随机端口映射时,也就是docker run -P时,会自动随机映射EXPOSE端口。

  • 格式

    1
    EXPOSE <端口1>[<端口2>...]

9.WORKDIR指定工作目录

  • 使用WORKDIR指令可以来指定工作目录(或者称为当前目录),以后各层的当前目录就被改成指定的目录,如果目录不存在WORKDIR会帮你建立目录。
  • 格式
    1
    WORKDIR <工作目录路径>
  • 初学者还会把Dockerfile当成Shell脚本来写,可能会出现这样的错误
  • 这种错误是对Dockerfile构建分层存储的概念不了解导致的,每执行一个指令都是启动一个容器。
  • 在Docker中两个RUN命令的执行环境根本不同,是两个完全不同的容器,所以可能到不到想要的效果
    1
    2
    3
    4
    5
    RUN cd /app
    RUN echo 'hello' > world.txt

    # 下面这个写法可以正确执行
    RUN cd /app && RUN echo 'hello' > world.txt

10.USER指定当前用户

  • USER是帮你切换到指定用户,这个用户必须事先建立好,否则无法切换。

  • USER指令和WORKDIR相似,都是改变环境状态并影响以后的层。

  • WORKDIR是改变工作目录,USER则是改变之后层的执行RUN,CMD以及ENTRYPOINT这类命令的身份。

  • 格式

    1
    USER <用户名>[:<用户组>]
  • 示例

    1
    2
    3
    RUN groupadd -r redis && useradd -r -g redis redis
    USER redis
    RUN [ "redis-server" ]
  • 如果以root执行脚本,在执行期间希望改变身份,不要使用su或者sudo,都需要比较麻烦的配置。

  • 在TTY缺失的环境下经常出错。建议使用gosu

  • 示例

    1
    2
    3
    4
    5
    6
    7
    8
    # 建立 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" ]

    11.HEALTHCHECK健康检查

  • HEALTHECK 指令是告诉Docker应该如果进行判断容器的状态是否正常。

  • 在没有HEALTHCHECK指令前,Docker引擎只可以通过容器内主进程是否退出来判断容器是否状态异常。很多情况下没有问题,但是如果程序进行死锁状态,或者死循环状态,应用程序并不退出,但是该容器已经无法提供服务。

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

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

  • 在HEALTHCHECK [选项] CMD后面的明林,格式和ENTRYPOINT一样,分为SHELL格式和exec格式,

  • 命令返回值决定了该次健康检查的成功与否 0:成功 1:失败 2:保留

  • 格式

    1
    2
    HEALTHCHECK [选项] CMD <命令>:设置检查容器健康状态的命令
    HEADTHCHECk NONE:如果基础镜像有健康检查指令,使用这行可以屏蔽掉其健康检查指令。
  • HEALTHCHECK支持下列选项

    1
    2
    3
    --interval=<间隔> :两次健康检查的间隔,默认三十秒。
    --timeout=<时长>:健康检查命令运行超时时间,如果超过这个时间,本次健康检查就被视为失败,默认30s。
    --retries=<次数>:当连续失败指定次数后,则将容器状态视为unhealthy,默认3s。
  • 示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    # Dockerfile
    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
    # build
    docker build -t myweb:v1 .

    # 启动
    docker run -d --name web -p 80:80 myweb:v1

    # 查看状态
    $ docker container ls
    CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
    03e28eb00bd0 myweb:v1 "nginx -g 'daemon off" 3 seconds ago Up 2 seconds (health: starting) 80/tcp, 443/tcp web

    # 再次查看
    $ docker container ls
    CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
    03e28eb00bd0 myweb:v1 "nginx -g 'daemon off" 18 seconds ago Up 16 seconds (healthy) 80/tcp, 443/tcp web
    # 如果健康检查连续失败超过了重试次数,状态就会变为 (unhealthy)。

    12.ONBUILD

  • ONBUILD是一个特殊的指令,它后面跟的是其他指令,比如RUN COPY等,而这些指令,在当前镜像构建时并不会被执行。只把当前镜像为基础镜像,去构建下一级镜像的时候才会被执行。

  • Dockerfile中的其他指令都是为了定制当前镜像而准备的,唯有ONBUILD是为了帮助别人定制自己而准备的。

  • 格式

    1
    ONBUILD <其他指令>
  • 示例

    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
    36
    37
    38
    # 假设我们要制作 Node.js 所写的应用的镜像。
    # 我们都知道 Node.js 使用 npm 进行包管理,所有依赖、配置、启动信息等会放到 package.json 文件里。
    # 在拿到程序代码后,需要先进行 npm install 才可以获得所有需要的依赖。然后就可以通过 npm start 来启动应用

    # Dockerfile
    FROM node:slim
    RUN mkdir /app
    WORKDIR /app
    COPY ./package.json /app
    RUN [ "npm", "install" ]
    COPY . /app/
    CMD [ "npm", "start" ]

    # 如果很多个项目都使用大致相同的Dockerfile
    # 这样我们可以做一个基础镜像,这样基础镜像更新,各个项目就不用同步Dockerfile的变化了。
    # 这样我们可以吧项目相关的构建指令拿出来,放到子项目里面。
    # 假设这个基础镜像的名字为my-node的话,各个项目内的自己的Dockerfile就变为

    FROM my-node
    COPY ./package.json /app
    RUN [ "npm", "install" ]
    COPY . /app/

    # 如果只针对某个项目进行修改命令?可以使用ONBUILD解决

    FROM node:slim
    RUN mkdir /app
    WORKDIR /app
    ONBUILD COPY ./package.json /app
    ONBUILD RUN [ "npm", "install" ]
    ONBUILD COPY . /app/
    CMD [ "npm", "start" ]

    # 如果其余项目没有自定义改变的话,则是下面的

    FROM my-node

    # 只有这么一行。当在各个项目目录中,用这个只有一行的 Dockerfile 构建镜像时,之前基础镜像的那三行 ONBUILD 就会开始执行,成功的将当前项目的代码复制进镜像、并且针对本项目执行 npm install,生成应用镜像。

    3.操作容器

    1.启动

    1.新建并启动

  • 1.输出内容

    1
    docker run centos /bin/echo 'hello world'
  • 2.启动一个bash终端,允许用户进行交互

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # -t 选项让Docker分配一个伪终端(pseudo-tty)并绑定到容器的标准输入上
    # -i 则让容器的标准保持打开。
    docker run -t -i centos /bin/bash

    # 交互模式下,用户可以通过创建的终端来输入命令

    root@af8bae53bdd3:/# pwd
    /

    root@af8bae53bdd3:/# ls
    bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
  • 3.当利用docker run来创建容器时,Docker在后台运行的标准操作包括:

    1
    2
    3
    4
    5
    6
    7
    1.检查本地是否存在指定的镜像,不存在就从公有仓库下载。
    2.利用镜像创建并启动一个容器
    3.分配一个文件系统,并在只读的镜像层外面挂在一层可读写层。
    4.从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中去
    5.从地址池配置一个ip地址给容器
    6.执行用户指定的应用程序
    7.执行完毕后容器被终止。

    2.启动已终止容器

  • 使用下方命令,可以将一个终止的容器启动运行。

    1
    docker container start

2.守护态运行

1.后台运行

  • 大部分情况,需要让Docker在后台运行而不是直接吧执行命令的结果输出在当前宿主机下。此时,可以通过添加-d参数来实现。
  • 不使用-d,会在输出结果(STDOUT)打印到宿主机上面
    1
    2
    3
    4
    5
    6
    docker run centos /bin/sh -c "while true; do echo hello world; sleep 1; done"

    hello world
    hello world
    hello world
    hello world
  • 如果使用了-d,此时并不会吧输出结果打印到宿主机上面(输出结果可以用docker logs查看)
    1
    docker run -d centos /bin/sh -c "while true; do echo hello world; sleep 1; done"
  • 容器是否会长久运行,是和docker run指定的命令有关,和-d无关。
  • 使用-d参数启动后会返回一个唯一的id,也可以通过docker container ls命令来查看容器信息。
    1
    2
    3
    ocker container ls
    CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
    77b2dc01fe0f centos /bin/sh -c 'while tr 2 minutes ago Up 1 minute agitated_wright
  • 要获取容器信息,可以通过docker container logs命令
    1
    docker container logs [container ID or NAMES]

3.终止

  • 1.终止容器

    1
    docker container stop containerID
  • 2.查看终止的容器

    1
    docker container ls -a
  • 3.重新启动终止的容器

    1
    docker container start containerID
  • 4.重启一个运行态的容器

    1
    docker container restart containerID

4.进入容器

5.导出和导入

6.删除

4.镜像托管

1.Docker Hub

1.Github账号连接到Docker Hub

  • 打开 Docker Hub -> 登录 -> 点击Create -> 选择 Create Automated Build -> Linked Accounts -> Link Github -> Public and Private (Recommended) -> Authorize application -> 输入 Github 密码

2.自动构建

  • 打开 Docker Hub -> 登录 -> 点击Create -> 选择 Create Automated Build -> 选择Github -> 选择仓库 -> 设置镜像名、描述、public 还是 private -> create -> 在 Build Settings 里选择分支、设置Dockerfile的地址、设置每次构建的标签 -> 更新github仓库,触发自动构建 -> 查看构建详情

5.容器间互相访问

  • 1.可以通过设置link
  • 2.开放对外端口

1.启动mysql,同时开放了9333对外端口

1
docker run --name v2ray-mysql -e MYSQL_ROOT_PASSWORD=admin -d -p 9333:3306 mysql mysqld --default-authentication-plugin=mysql_native_password

2.其余容器访问

  • 启动容器

    1
    docker run -d -p 9001:80 --link v2ray-mysql
  • link方式连接mysql

    1
    2
    3
    4
    5
    6
    7
    8
    # 这里 --link了mysql容器的name
    # 配置文件中可以按照下方的内容进行配置

    host: 'v2ray-mysql',
    user: 'root',
    password: 'admin',
    database: 'test',
    port: 3306
  • 对外端口方式

    1
    2
    3
    4
    5
    6
    7
    8
    # 启动容器去掉link即可
    # 配置文件中可以按照下方的内容进行配置

    host: 'ip',
    user: 'root',
    password: 'admin',
    database: 'test',
    port: 9333

3.其余设置解析

  • 示例命令
    1
    2
    3
    4
    5
    6
    7
    8
    docker run --name test-mysql -e MYSQL_ROOT_PASSWORD=admin -d -p 9333:3306 mysql mysqld --default-authentication-plugin=mysql_native_password -e TZ=Asia/Shanghai

    --name test-mysql # 给mysql取名
    -e MYSQL_ROOT_PASSWORD=newPassword # 初始化默认密码
    -d # 后台启动
    -p 9333:3306 # 宿主机端口:内部端口
    mysqld --default-authentication-plugin=mysql_native_password # 兼容mysql模块并未完全支持MySQL 8的caching_sha2_password加密
    -e TZ=Asia/Shanghai # 修改为上海的时区

6.数据管理

数据卷是一个可供一个或多个容器使用的特殊目录,它绕过UFS,可以提供很多有用的特性。

  • 数据卷可以在容器之间共享和重用
  • 对数据卷的修改立马生效
  • 对数据卷的更新,不会影响到镜像。
  • 数据卷默认后一直存在,即使容器被删除。
  • 数据卷的使用类似于Linux下对目录或文件进行mount,镜像中的被指定为挂在点的目录中的文件会被复制到数据卷中(仅数据卷为空时会复制)

1.数据卷

1.创建一个数据卷

1
docker volume create my-vol
  • 查看所有数据卷

    1
    2
    3
    4
    docker volume ls

    DRIVER VOLUME NAME
    local my-vol
  • 在主机里使用以下命令可以查看指定数据卷的信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    docker volume inspect my-vol

    [
    {
    "Driver": "local",
    "Labels": {},
    "Mountpoint": "/var/lib/docker/volumes/my-vol/_data",
    "Name": "my-vol",
    "Options": {},
    "Scope": "local"
    }
    ]

2.启动一个挂在数据卷的容器

  • 在用docker run命令的时候,使用–mount标记来将数据卷挂载到容器里。在一次docker run中可以挂载多个数据卷。
  • 下面创建一个名为web的容器,并加载一个数据卷到容器的/usr/share/nginx/html目录
    1
    2
    3
    4
    5
    docker run -d -P \
    --name web \
    # -v my-vol:/usr/share/nginx/html \
    --mount source=my-vol,target=/usr/share/nginx/html \
    nginx:alpine

3.查看数据卷的具体信息

  • 在主机里使用以下命令可以查看web容器的信息
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    docker inspect web

    # 数据卷信息在Mounts Key下面

    "Mounts": [
    {
    "Type": "volume",
    "Name": "my-vol",
    "Source": "/var/lib/docker/volumes/my-vol/_data",
    "Destination": "/usr/share/nginx/html",
    "Driver": "local",
    "Mode": "",
    "RW": true,
    "Propagation": ""
    }
    ],

4.删除数据卷

1
docker volume rm my-vol
  • 数据卷是被设计用来持久化数据的,它的生命周期独立于容器,Docker不会在容器被删除后自动删除数据卷,并且也不存在垃圾回收这样的机制来处理没有任何容器引用的数据卷。如果需要在删除容器的同时移除数据卷。可以在删除容器的时候用下面的命令删除。

    1
    docker rm -v
  • 无主的数据卷可能会占据很多空间,要清理使用以下命令

    1
    docker volume prune

2.挂载主机目录

  • 使用 --mount标记可以指定挂载一个本地主机的目录到容器中去。

    下面的命令加载主机的 /src/webapp 目录到容器的 /usr/share/nginx/html目录。这个功能在进行测试的时候十分方便,比如用户可以放置一些程序到本地目录中,来查看容器是否正常工作。本地目录的路径必须是绝对路径,以前使用 -v 参数时如果本地目录不存在 Docker 会自动为你创建一个文件夹,现在使用 –mount 参数时如果本地目录不存在,Docker 会报错。

    1
    2
    3
    4
    5
    docker run -d -P \
    --name web \
    # -v /src/webapp:/usr/share/nginx/html \
    --mount type=bind,source=/src/webapp,target=/usr/share/nginx/html \
    nginx:alpine
  • Docker挂载主机目录的默认权限是读写,用户也可以通过增加readonly指定为只读。加了readonly之后,就挂载为只读了。如果你在容器内/usr/share/nginx/html 目录新建文件,会显示如下错误
1
2
3
4
5
6
7
8
/usr/share/nginx/html # touch new.txt
touch: new.txt: Read-only file system

docker run -d -P \
--name web \
# -v /src/webapp:/usr/share/nginx/html:ro \
--mount type=bind,source=/src/webapp,target=/usr/share/nginx/html,readonly \
nginx:alpine

3.挂载一个本地主机文件作为数据卷

  • –mount 标记也可以从主机挂载单个文件到容器中
  • 下面的命令作用是可以记住在容器中输入的命令
    1
    2
    3
    4
    5
    6
    7
    8
    9
    docker run --rm -it \
    # -v $HOME/.bash_history:/root/.bash_history \
    --mount type=bind,source=$HOME/.bash_history,target=/root/.bash_history \
    ubuntu:18.04 \
    bash

    root@2affd44b4667:/# history
    1 ls
    2 diskutil list

7.使用网络

  • Docker允许通过外部访问容器或容器互联的方式来提供网络服务。

1.外部访问容器

  • 容器中可以运行一些网络应用,要让外部也可以访问这些应用,可以通过-P或者-p参数来指定端口映射。
  • 当使用-P标记时,Docker会随机映射一个端口到内部容器开放的网络端口。
  • 使用docker container ls 可以看到,本地主机的32768被映射到了容器的80端口。此时访问本机的32768端口即可访问容器内NGINX默认页面。
1
2
3
4
5
$ docker run -d -P nginx:alpine

$ docker container ls -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
fae320d08268 nginx:alpine "/docker-entrypoint.…" 24 seconds ago Up 20 seconds 0.0.0.0:32768->80/tcp bold_mcnulty
  • 可以通过docker logs命令来查看访问记录
    1
    2
    $ docker logs fa
    172.17.0.1 - - [25/Aug/2020:08:34:04 +0000] "GET / HTTP/1.1" 200 612 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:80.0) Gecko/20100101 Firefox/80.0" "-"
  • p则可以指定要映射的端口,并且,在一个指定端口上只可以绑定一个容器。支持的格式有
    1
    ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort

2.映射所有接口地址

  • 使用hostPort:containerPort格式本地的80端口映射到容器的80端口,可以执行
  • 此时默认会绑定本地所有接口上的所有地址。
    1
    $ docker run -d -p 80:80 nginx:alpine

3.映射到指定地址的指定端口

  • 可以用ip:hostPort:containerPort格式指定映射使用一个特定地址,比如localhost地址127.0.0.1
    1
    docker run -d -p 127.0.0.1:80:80 nginx:alpine

4.映射到指定地址的任意端口

  • 使用ip:containerPort绑定localhost的任意端口到容器的80端口,本地主机自动分配一个端口。
    1
    docker run -d -p 127.0.0.1::80 nginx:alpine
  • 可以使用udp标记来指定udp端口
    1
    docker run -d -p 127.0.0.1:80:80/udp nginx:alpine

5.查看映射端口配置

  • 使用docker port来查看当前映射的端口配置,也可以查看绑定的地址
    1
    2
    docker port fa 80
    0.0.0.0:32768
  • 容器有自己的内部网址和ip地址(使用docker inspect查看,Docker还可以有一个可变的网络配置)
  • -p 标记可以多次使用绑定多个端口
    1
    2
    3
    4
    docker run -d \
    -p 80:80 \
    -p 443:443 \
    nginx:alpine

2.容器互联

  • 不建议使用–link

1.新建网络

1
docker network create -d bridge my-net
  • -d参数指定Docker网络类型,有bridge、overlay。其中overlay网络类型用于 Swarm mode。

2.连接容器

  • 运行一个容器并连接到新建的my-net网络

    1
    docker run -it --rm --name busybox1 --network my-net busybox sh
  • 打开新的终端,再运行一个容器并加入到my-net网络

    1
    docker run -it --rm --name busybox2 --network my-net busybox sh
  • 再打开一个新的终端查看容器信息

    1
    2
    3
    4
    5
    docker container ls

    CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
    b47060aca56b busybox "sh" 11 minutes ago Up 11 minutes busybox2
    8720575823ec busybox "sh" 16 minutes ago Up 16 minutes busybox1
  • 通过ping来证明两个容器是互联的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # busybox1

    PING busybox2 (172.19.0.3): 56 data bytes
    64 bytes from 172.19.0.3: seq=0 ttl=64 time=0.072 ms

    # busybox2

    / # ping busybox1
    PING busybox1 (172.19.0.2): 56 data bytes
    64 bytes from 172.19.0.2: seq=0 ttl=64 time=0.064 ms
    64 bytes from 172.19.0.2: seq=1 ttl=64 time=0.143 ms
  • 如果多个容器互相连接,推荐使用Docker Compose

3.配置DNS

  • 利用虚拟文件来挂载容器的三个相关配置文件。

    1
    2
    3
    4
    $ 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。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    {
    "dns" : [
    "114.114.114.114",
    "8.8.8.8"
    ]
    }
    # 执行下方命令判断是否启用

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

    nameserver 114.114.114.114
    nameserver 8.8.8.8
  • 手动指定容器的hostname

    1
    2
    docker run -h HOSTNAME 或者 --hostname=HOSTNAME
    # 会被写到容器中的 /etc/hostname 和 /etc/hosts
  • 手动指定dns

    1
    2
    docker run --dns=IP_ADDRESS
    # 会被写到容器中的/etc/resolv.conf
  • 手动指定容器的搜索域,当设置搜索域为.example.com时,在搜索一个名为host的主机时,DNS不仅搜索host,还会搜索host.example.com

  • 如果容器启动时没有指定最后两个参数,Docker会默认用主机上的/etc/resolv.conf来配置容器 dns dns-search -h

8.高级网络配置

7.常见错误

1.错误1

1
docker: Error response from daemon: OCI runtime create failed: container_linux.go:345: starting container process caused "process_linux.go:430: container init caused \"write /proc/self/attr/keycreate: permission denied\"": unknown.
  • 1.修改下面文件SELINUX的值为disabled,然后重启
    1
    /etc/selinux/config

脚本

1.一键停止、删除镜像

  • 创建一个sh文件,然后复制下面内容到文件中
  • 执行时:sh 脚本名.sh 镜像名称
    1
    2
    3
    CONTAINERID=`docker ps|grep -v grep|grep $1|awk '{print $1}'`;
    docker stop ${CONTAINERID} && docker rm ${CONTAINERID};
    docker image rm $1;

如有侵权行为,请点击这里联系我删除

如发现疑问或者错误点击反馈

备注