img

文章目录

  • docker是什么以及为什么用docker
    • docker是什么
    • Docker的好处
    • 容器和虚拟机的区别
    • Docker基本概念
  • docker安装和测试
    • ubuntu安装方法
      • 准备工作
      • 使用 apt安装
      • 卸载docker
      • 启动 Docker CE
      • 建立 docker 用户组
  • Docker 常用命令与操作
    • 基础类
    • 日志类
    • 容器类
      • 查看容器信息
      • 容器同步命令
      • 容器操作命令
      • 查看容器的root用户密码
      • 容器于宿主拷贝文件
      • 运行一个新容器,同时为它命名、端口映射、文件夹映射
      • 一个容器连接到另一个容器
      • 导入导出容器
    • 镜像操作
      • 远程镜像
      • 本地镜像
    • docker run
  • 操作docker镜像
    • 获取镜像
    • 运行
    • 查看本地已有的镜像
    • 删除本地镜像
      • 用 ID、镜像名、摘要删除镜像
      • Untagged 和 Deleted
      • 用 docker image ls 命令来配合
    • 利用 commit 理解镜像构成
      • 慎用 docker commit
    • 实例-使用 Dockerfile 定制镜像
      • FROM 指定基础镜像
      • RUN 执行命令
      • 构建镜像
      • 镜像构建上下文(Context)
      • 其它 docker build 的用法
    • Dockerfile 指令详解
  • 操作Docker容器
    • 启动容器
      • 新建并启动
      • 启动已终止容器
    • 后台运行
    • 终止容器
    • 进入容器
    • 导出和导入容器
      • 导出容器
      • 导入容器快照
  • 操作仓库
    • Docker Hub
      • 注册
      • 登录
      • 拉取镜像
      • 推送镜像
      • 自动创建
    • 私有仓库
      • 安装运行 docker-registry
      • 在私有仓库上传、搜索、下载镜像
      • 注意事项
    • 私有仓库高级配置
      • 准备站点证书
      • 配置私有仓库
      • 生成 http 认证文件
      • 编辑 docker-compose.yml
      • 修改 hosts
      • 启动
      • 测试私有仓库功能
      • 注意事项
  • Docker 数据管理
    • 数据卷
      • 选择 -v 还是 -–mount 参数
      • 创建一个数据卷
      • 启动一个挂载数据卷的容器
      • 查看数据卷的具体信息
      • 删除数据卷
    • 挂载主机目录
      • 选择 -v 还是 -–mount 参数
      • 挂载一个主机目录作为数据卷
      • 查看数据卷的具体信息
      • 挂载一个本地主机文件作为数据卷
  • Docker Compose 项目
    • Compose 简介
    • 安装与卸载
    • 实例-Web计数
      • 术语
      • 准备
      • 创建 Dockerfile 文件
      • 创建 docker-compose.yml
      • 使用 Compose 命令构建和运行您的应用
    • Compose 命令说明
    • Compose 模板文件
  • 实战:docker-compose部署redis-cluster
    • 环境
    • 搭建
    • 查看集群状态


docker是什么以及为什么用docker

docker是什么

docker的思想来源于港口码头工人搬运货物。

在这里插入图片描述
标准化集装箱前后的航运对比
docker出现之前,部署软件到不同环境所需的工作量巨大。
在这里插入图片描述
使用Docker前后软件交付的对比

Docker的好处

  • 替代虚拟机:用户只需要关心应用程序,而不是操作系统。
  • 软件原型:快速体验软件,同时避免干扰目前的设置或配备一个虚拟机的麻烦。比如要测试不同版本的 redis,可以使用docker开启多个ubuntu镜像,然后在不同的ubuntu镜像测试不同的redis版本。
  • 打包软件:docker镜像对Linux用户没有依赖,可以将构建的镜像运行在不同的Linux机器上。
  • 让微服务成为可能:docker有助于将一个复杂的系统分解成一系列可组合的部分,有利于用户更好地思考其服务。
  • 网络建模:可以在一台机器上启动数百个隔离的容器,因而对网络进行建模轻而易举。这对于显示世界场景的测试非常有用。
  • 降低调试支出:可以让生产、测试、部署统一环境,而不因不同环境:失效的库、有问题的依赖、更新被错误实施或是执行顺序有误,甚至可能根本没有执行以及无法出现的错误等等。
  • 启用持续交付:更利于构建一个基于流水线的软件交付模型。

容器和虚拟机的区别

在这里插入图片描述
vm(虚拟机)与docker(容器)框架,直观上来讲vm多了一层guest OS,同时Hypervisor会对硬件资源进行虚拟化,docker直接使用硬件资源,所以资源利用率相对docker低也是比较容易理解的。

服务器虚拟化解决的核心问题是资源调配,而容器解决的核心问题是应用开发、测试和部署。
虚拟机技术通过Hypervisor层抽象底层基础设施资源,提供相互隔离的虚拟机,通过统一配置、统一管理,计算资源的可运维性,以及资源利用率都能够得到有效的提升。同时,虚拟机提供客户机操作系统,客户机变化不会影响宿主机,能够提供可控的测试环境,更能够屏蔽底层硬件甚至基础软件的差异性,让应用做到的广泛兼容。然而,再牛逼的虚拟化技术,都不可避免地出现计算、IO、网络性能损失,毕竟多了一层软件,毕竟要运行一个完整的客户机操作系统。
容器技术严格来说并不是虚拟化,没有客户机操作系统,是共享内核的。容器可以视为软件供应链的集装箱,能够把应用需要的运行环境、缓存环境、数据库环境等等封装起来,以最简洁的方式支持应用运行,轻装上阵,当然是性能更佳。Docker镜像特性则让这种方式简单易行。当然,因为共享内核,容器隔离性也没有虚拟机那么好。

Docker基本概念

Docker 包括三个基本概念:
● 镜像(Image):Docker 镜像(Image),就相当于是一个 root 文件系统。比如官方镜像 ubuntu:16.04 就包含了完整的一套 Ubuntu16.04 最小系统的 root 文件系统。
● 容器(Container):镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的类和实例一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。
● 仓库(Repository):仓库可看成一个代码控制中心,用来保存镜像。

Docker 使用客户端-服务器 (C/S) 架构模式,使用远程API来管理和创建Docker容器。
Docker 容器通过 Docker 镜像来创建。
容器与镜像的关系类似于面向对象编程中的对象与类。

Docker面向对象
容器对象
镜像

在这里插入图片描述在这里插入图片描述

docker安装和测试

ubuntu安装方法

准备工作

系统要求
Docker CE 支持以下版本的 Ubuntu 操作系统:
● Ubuntu Focal 20.04 (LTS)
● Ubuntu Bionic 18.04 (LTS)
● Ubuntu Xenial 16.04 (LTS)

Docker CE 可以安装在 64 位的 x86 平台或 ARM 平台上。Ubuntu 发行版中,LTS(Long-Term-Support)长期支持版本,会获得 5 年的升级维护支持,这样的版本会更稳定,因此在生产环境中推荐使用 LTS 版本,当前最新的 LTS 版本为 Ubuntu 20。
卸载旧版本
旧版本的 Docker 称为 docker 或者 docker-engine,使用以下命令卸载旧版本:

sudo apt-get remove docker docker-engine docker.io containerd runc

Ubuntu 16.04 +
Ubuntu 16.04 + 上的 Docker CE 默认使用 overlay2 存储层驱动,无需手动配置。

使用 apt安装

由于 apt 源使用 HTTPS 以确保软件下载过程中不被篡改。因此,我们首先需要添加使用 HTTPS 传输的软件包以及 CA 证书。

 sudo apt-get updatesudo apt-get install \apt-transport-https \ca-certificates \curl \software-properties-common

鉴于国内网络问题,强烈建议使用国内源,官方源请在注释中查看。
为了确认所下载软件包的合法性,需要添加软件源的 GPG 密钥。

# 国内源,使用这里的源
curl -fsSL https://mirrors.ustc.edu.cn/docker-ce/linux/ubuntu/gpg | sudo apt-key add -# 官方源,用了国内源,这里就不用再执行
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

然后,我们需要向 source.list 中添加 Docker 软件源

# 国内源sudo add-apt-repository \"deb [arch=amd64] https://mirrors.ustc.edu.cn/docker-ce/linux/ubuntu \$(lsb_release -cs) \stable"# 官方源
# $ sudo add-apt-repository \
#    "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
#    $(lsb_release -cs) \
#    stable"

以上命令会添加稳定版本的 Docker CE APT 镜像源,如果需要最新或者测试版本的 Docker CE 请将 stable 改为 edge 或者 test。从 Docker 17.06 开始,edge test 版本的 APT 镜像源也会包含稳定版本的 Docker。

安装 Docker CE
更新 apt 软件包缓存,并安装 docker-ce:

 sudo apt-get updatesudo apt-get install docker-ce docker-ce-cli containerd.io

安装需要2~3分钟。

如果需要安装指定版本,请参考:https://docs.docker.com/engine/install/ubuntu/ To install a specific version of Docker Engine
查看一下docker版本

docker -v

在这里插入图片描述

卸载docker

如果需要卸载,不需要就别卸载。
卸载 Docker Engine, CLI,和Containerd 包:

sudo apt-get purge docker-ce docker-ce-cli containerd.io

删除残留文件:

sudo rm -rf /var/lib/docker

启动 Docker CE

● 开机启动:sudo systemctl enable docker
● 启动docker:sudo systemctl start docker
● 停止docker:sudo systemctl stop docker

建立 docker 用户组

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

 sudo groupadd docker

将当前用户加入 docker 组:

 sudo usermod -aG docker $USER

退出当前终端并重新登录(如果是通过类似xshell之类的连接,再开一个连接即可),进行如下测试。
测试 Docker 是否安装正确

 docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
0e03bdcc26d7: Pull complete 
Digest: sha256:4cf9c47f86df71d48364001ede3a4fcd85ae80ce02ebad74156906caff5378bc
Status: Downloaded newer image for hello-world:latestHello from Docker!
This message shows that your installation appears to be working correctly.To generate this message, Docker took the following steps:1. The Docker client contacted the Docker daemon.2. The Docker daemon pulled the "hello-world" image from the Docker Hub.(amd64)3. The Docker daemon created a new container from that image which runs theexecutable that produces the output you are currently reading.4. The Docker daemon streamed that output to the Docker client, which sent itto your terminal.To try something more ambitious, you can run an Ubuntu container with:$ docker run -it ubuntu bashShare images, automate workflows, and more with a free Docker ID:https://hub.docker.com/For more examples and ideas, visit:https://docs.docker.com/get-started/

若能正常输出以上信息,则说明安装成功。

Docker 常用命令与操作

基础类

查看docker信息

# 查看docker版本
docker version
# 显示docker系统的信息
docker info
# 日志信息
docker logs
# 故障检查
service docker status
# 启动关闭docker
sudo service docker start|stop

日志类

查看容器日志

docker logs -f <容器名orID>

docker ps 没有响应 日志查询

# grep 所有容器的config.json
docker logs [conID]
# 确认问题后
# 该config.json 中有该容器1号进程的pid
kill -9 pid

容器类

docker容器可以理解为在沙盒中运行的进程
这个沙盒包含了该进程运行所必须的资源,包括文件系统、系统类库、shell 环境等等。但这个沙盒默认是不会运行任何程序的。你需要在沙盒中运行一个进程来启动某一个容器。这个进程是该容器的唯一进程,所以当该进程结束的时候,容器也会完全的停止。

查看容器信息

# 查看当前运行的容器
docker ps
# 查看全部容器
docker ps -a
# 查看全部容器的id和信息
docker ps -a -q
# 查看全部容器占用的空间
docker ps -as
# 查看一个正在运行容器进程,支持 ps 命令参数
docker top
# 查看容器的示例id
sudo docker inspect -f  '{{.Id}}' [id]
# 检查镜像或者容器的参数,默认返回 JSON 格式
docker inspect
# 返回 ubuntu:16.04  镜像的 docker 版本
docker inspect --format '{{.DockerVersion}}' ubuntu:16.04
docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' ubuntu:14.04

容器同步命令

# 保存对容器的修改
docker commit
# 保存某个容器成为一个镜像
docker commit -a "user" -m "commit info" [CONTAINER] [imageName]:[imageTag]
# 推送一个容器到中心仓库
docker login --username=[userName] --password=[pwd] [registryURL]
## 建议登录后查看 docker info
docker tag [imageID] [remoteURL]:[imageTag]
docker push [remoteURL]:[imageTag]
# 拉取提交的容器
docker pull [remoteURL]:[imageTag]
# 对比容器的改动
docker diff
# 附加到一个运行的容器上
docker attach

容器操作命令

创建删除容器

# 创建一个容器命名为 test 使用镜像daocloud.io/library/ubuntu
docker create -it --name test daocloud.io/library/ubuntu
# 创建并启动一个容器 名为 test 使用镜像daocloud.io/library/ubuntu
docker run --name test daocloud.io/library/ubuntu
# 删除一个容器
docker rm [容器id]
# 删除所有容器
docker rm `docker ps -a -q`
# 根据Dockerfile 构建
docker build -t [image_name] [Dockerfile_path]

docker容器随系统自启

docker run --restart=always

● no – 默认值,如果容器挂掉不自动重启
● on-failure – 当容器以非 0 码退出时重启容器,同时可接受一个可选的最大重启次数参数 (e.g. on-failure:10).
● always – 不管退出码是多少都要重启
容器资源限制参数

# 限制内存最大使用
-m 1024m --memory-swap=1024m
# 限制容器使用CPU
--cpuset-cpus="0,1"

把一个正在运行的容器保存为镜像

docker commit <CONTAIN-ID> <IMAGE-NAME>

启动停止容器等操作

docker start|stop|restart [id]
# 暂停|恢复 某一容器的所有进程
docker pause|unpause [id]
# 杀死一个或多个指定容器进程
docker kill -s KILL [id]
# 停止全部运行的容器
docker stop `docker ps -q`
# 杀掉全部运行的容器
docker kill -s KILL `docker ps -q`

交互式进入容器

docker exec -it {{containerName or containerID}} bash
docker exec -i {{containerName or containerID}} bash
docker exec -t {{containerName or containerID}} bash
docker exec -d {{containerName or containerID}} bash

● 只用 -i 参数,由于没有分配伪终端,看起来像pipe执行一样。但是执行结果、命令返回值都可以正确获取
● 只用 -t 参数,则可以看到一个 console 窗口,但是执行命令会发现由于没有获得stdin的输出,无法看到命令执行情况
● 使用 -it 时,则和我们平常操作 console 界面类似,而且也不会像attach方式因为退出,导致整个容器退出
● 使用 -d 参数,在后台执行一个进程。如果一个命令需要长时间进程,会很快返回

Docker attach
Docker attach可以attach到一个已经运行的容器的stdin,然后进行命令执行的动作

docker attach {{containerName or containerID}} 

需要注意的是,如果从这个stdin中exit,会导致容器的停止

查看容器的root用户密码

docker logs <容器名orID> 2>&1 | grep '^User: ' | tail -n1 

因为Docker容器启动时的root用户的密码是随机分配的。所以,通过这种方式就可以得到容器的root用户的密码

容器于宿主拷贝文件

docker cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH|-
docker cp [OPTIONS] SRC_PATH|- CONTAINER:DEST_PATH
# 本地文件上传到对应容器的目录
docker cp local.sh [CONTAINERid]:[TagPath]

运行一个新容器,同时为它命名、端口映射、文件夹映射

以redmine镜像为例

docker run --name redmine -p 9003:80 -p 9023:22 -d -v /var/redmine/files:/redmine/files -v /var/redmine/mysql:/var/lib/mysql sameersbn/redmine 

一个容器连接到另一个容器

docker run -i -t --name sonar -d -link mmysql:db  tpires/sonar-server sonar 

导入导出容器

# 支持远程文件 .tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz 
docker import 
# 导出 
docker export [id] >~/Downloads/ubuntu_nexus.tar 

导出后导入(exported-imported))的容器会丢失所有的提交历史,无法回滚。

镜像操作

远程镜像

docker login
docker search
# 搜索处收藏数不小于 3 ,并且能够自动化构建的  django 镜像,并且完整显示镜像描述
docker search -s 3 --automated --no-trunc django
docker pull
# 拉取ubuntu最新的镜像
docker pull ubuntu:latest
# 服务器拉取个人动态,可选择时间区间
docker events
# 拉取个人从 2015/07/20 到 2015/08/08 的个人动态
docker events --since="20150720" --until="20150808"

镜像同步操作

# 标记本地镜像,将其归入某一仓库
docker tag
# 将 ID 为 5db5f84x1261 的容器标记为 mine/lnmp:0.2 镜像
docker tag 5db5f84x1261 mine/lnmp:0.2
# 将镜像推送至远程仓库,默认为 Docker Hub
docker push

本地镜像

# 列出本地所有镜像
docker images
# 本地镜像名为 ubuntu 的所有镜像
docker images ubuntu
# 查看指定镜像的创建历史
docker history [id]
# 本地移除一个或多个指定的镜像
docker rmi
# 移除本地全部镜像
docker rmi `docker images -a -q`
# 指定镜像保存成 tar 归档文件, docker load 的逆操作
docker save
# 将镜像 ubuntu:14.04 保存为 ubuntu14.04.tar 文件
docker save -o ubuntu14.04.tar ubuntu:14.04
# 从 tar 镜像归档中载入镜像, docker save 的逆操作
docker load
# 上面命令的意思是将 ubuntu14.04.tar 文件载入镜像中
docker load -i ubuntu14.04.tar
docker load < /home/save.tar
# 构建自己的镜像
docker build -t <镜像名> <Dockerfile路径>
docker build -t xx/gitlab .

保存后再加载(saved-loaded)的镜像不会丢失提交历史和层,可以回滚
重新查看container的stdout

# 启动top命令,后台运行
$ ID=$(sudo docker run -d ubuntu /usr/bin/top -b)
# 获取正在running的container的输出
$ sudo docker attach $ID
top - 02:05:52 up  3:05,  0 users,  load average: 0.01, 0.02, 0.05
Tasks:  1 total,  1 running,  0 sleeping,  0 stopped,  0 zombie
Cpu(s):  0.1%us,  0.2%sy,  0.0%ni, 99.7%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Mem:    373572k total,  355560k used,    18012k free,    27872k buffers
Swap:  786428k total,        0k used,  786428k free,  221740k cached
^C$
$ sudo docker stop $ID

docker run

后台运行(-d)、并暴露端口

docker run -d -p 127.0.0.1:33301:22 centos6-ssh

run 命令详解

-a, --attach=[]            Attach to STDIN, STDOUT or STDERR 指定标准输入输出内容类型,可选 STDIN/STDOUT/STDERR 三项--add-host=[]              Add a custom host-to-IP mapping (host:ip)--blkio-weight=0            Block IO (relative weight), between 10 and 1000-c, --cpu-shares=0          CPU shares (relative weight)--cap-add=[]                Add Linux capabilities--cap-drop=[]              Drop Linux capabilities--cgroup-parent=            Optional parent cgroup for the container--cidfile=                  Write the container ID to the file--cpu-period=0              Limit CPU CFS (Completely Fair Scheduler) period--cpu-quota=0              Limit the CPU CFS quota--cpuset-cpus=              CPUs in which to allow execution (0-3, 0,1) 绑定容器到指定CPU运行--cpuset-mems=              MEMs in which to allow execution (0-3, 0,1) 绑定容器到指定MEM运行-d, --detach=false          Run container in background and print container ID 后台运行容器,并返回容器ID--device=[]                Add a host device to the container--dns=[]                    Set custom DNS servers 指定容器使用的DNS服务器,默认和宿主一致--dns-search=[]            Set custom DNS search domains 指定容器DNS搜索域名,默认和宿主一致-e, --env=[]                Set environment variables 设置环境变量--entrypoint=              Overwrite the default ENTRYPOINT of the image--env-file=[]              Read in a file of environment variables 从指定文件读入环境变量--expose=[]                Expose a port or a range of ports-h, --hostname=            Container host name 指定容器的hostname--help=false                Print usage-i, --interactive=false    Keep STDIN open even if not attached 以交互模式运行容器,通常与 -t 同时使用--ipc=                      IPC namespace to use-l, --label=[]              Set meta data on a container--label-file=[]            Read in a line delimited file of labels--link=[]                  Add link to another container--log-driver=              Logging driver for container--log-opt=[]                Log driver options--lxc-conf=[]              Add custom lxc options-m, --memory=              Memory limit--mac-address=              Container MAC address (e.g. 92:d0:c6:0a:29:33)--memory-swap=              Total memory (memory + swap), '-1' to disable swap--name=                    Assign a name to the container 为容器指定一个名称--net=bridge                Set the Network mode for the container  指定容器的网络连接类型,支持 bridge/host/none/container:<name|id> 四种类型--oom-kill-disable=false    Disable OOM Killer-P, --publish-all=false    Publish all exposed ports to random ports-p, --publish=[]            Publish a container's port(s) to the host--pid=                      PID namespace to use--privileged=false          Give extended privileges to this container--read-only=false          Mount the container's root filesystem as read only--restart=no                Restart policy to apply when a container exits--rm=false                  Automatically remove the container when it exits--security-opt=[]          Security Options--sig-proxy=true            Proxy received signals to the process-t, --tty=false            Allocate a pseudo-TTY 为容器重新分配一个伪输入终端,通常与 -i 同时使用-u, --user=                Username or UID (format: <name|uid>[:<group|gid>])--ulimit=[]                Ulimit options--uts=                      UTS namespace to use-v, --volume=[]            Bind mount a volume--volumes-from=[]          Mount volumes from the specified container(s)-w, --workdir=              Working directory inside the container

操作docker镜像

在之前的介绍中,我们知道镜像是 Docker 的三大组件之一。

Docker 运行容器前需要本地存在对应的镜像,如果本地不存在该镜像,Docker 会从镜像仓库下载该镜像。

本章将介绍更多关于镜像的内容,包括:

● 从仓库获取镜像;
● 管理本地主机上的镜像;
● 介绍镜像实现的基本原理。

获取镜像

之前提到过,Docker Hub 上有大量的高质量的镜像可以用,这里我们就说一下怎么获取这些镜像。

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

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

具体的选项可以通过 docker pull --help 命令看到,这里我们说一下镜像名称的格式。
● Docker 镜像仓库地址:地址的格式一般是 <域名/IP>[:端口号]。默认地址是 Docker Hub。
● 仓库名:如之前所说,这里的仓库名是两段式名称,即 <用户名>/<软件名>。对于 Docker Hub,如果不给出用户名,则默认为 library,也就是官方镜像。

比如:

ubuntu@VM-0-13-ubuntu:~$ docker pull  redis:6.0.8
6.0.8: Pulling from library/redis
d121f8d1c412: Pull complete 
2f9874741855: Pull complete 
d92da09ebfd4: Pull complete 
bdfa64b72752: Pull complete 
e748e6f663b9: Pull complete 
eb1c8b66e2a1: Pull complete 
Digest: sha256:1cfb205a988a9dae5f025c57b92e9643ec0e7ccff6e66bc639d8a5f95bba928c
Status: Downloaded newer image for redis:6.0.8
docker.io/library/redis:6.0.8

上面的命令中没有给出 Docker 镜像仓库地址,因此将会从 Docker Hub 获取镜像。而镜像名称是 redis:6.0.8,因此将会获取官方镜像 library/redis仓库中标签为 6.0.8 的镜像。

从下载过程中可以看到我们之前提及的分层存储的概念,镜像是由多层存储所构成。下载也是一层层的去下载,并非单一文件。下载过程中给出了每一层的 ID 的前 12 位。并且下载结束后,给出该镜像完整的 sha256 的摘要,以确保下载一致性。

在使用上面命令的时候,你可能会发现,你所看到的层 ID 以及 sha256 的摘要和这里的不一样。这是因为官方镜像是一直在维护的,有任何新的 bug,或者版本更新,都会进行修复再以原来的标签发布,这样可以确保任何使用这个标签的用户可以获得更安全、更稳定的镜像。

如果从 Docker Hub 下载镜像非常缓慢,可以参照 《2.1 ubuntu安装方法》 一节配置加速器。

运行

有了镜像后,我们就能够以这个镜像为基础启动并运行一个容器。以上面的 redis:6.08 为例,如果我们打算启动里面的 bash 并且进行交互式操作的话,可以执行下面的命令。

ubuntu@VM-0-13-ubuntu:~$ docker run -it --rm     redis:6.0.8     bash

然后进入到bash:

root@76353f71f834:/data#

docker run 就是运行容器的命令,具体格式我们会在 容器 一节进行详细讲解,我们这里简要的说明一下上面用到的参数。

● -it:这是两个参数,一个是 -i:交互式操作,一个是 -t 终端。我们这里打算进入 bash 执行一些命令并查看返回结果,因此我们需要交互式终端。
● --rm:这个参数是说容器退出后随之将其删除。默认情况下,为了排障需求,退出的容器并不会立即删除,除非手动 docker rm。我们这里只是随便执行个命令,看看结果,不需要排障和保留结果,因此使用 --rm 可以避免浪费空间。
● redis:6.0.8:这是指用 redis:6.0.8 镜像为基础来启动容器。
● bash:放在镜像名后的是命令,这里我们希望有个交互式 Shell,因此用的是 bash。

进入容器后,我们可以在 Shell 下操作,执行任何所需的命令。这里,我们执行了 redis-server,这是 Linux 常用的查看当前系统版本的命令,从返回的结果可以看到容器内是 安装了redis6.0.8 系统。
在这里插入图片描述最后我们通过 exit 退出了这个容器。

root@76353f71f834:~# exit 
exit
ubuntu@VM-0-13-ubuntu:~$

查看本地已有的镜像

使用 docker image ls查看本地已有的镜像。
比如:

$ 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
redis               				6.0.8               84c5f6e03bf0        4 weeks ago         104MB
docker                      latest              cf693ec9b5c7        3 weeks ago         105.1 MB
nginx                       latest              e43d811ce2f4        5 weeks ago         181.5 MB

删除本地镜像

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

$ docker image rm [选项] <镜像1> [<镜像2> ...]

用 ID、镜像名、摘要删除镜像

其中,<镜像> 可以是 镜像短 ID、镜像长 ID、镜像名 或者 镜像摘要。

比如我们有这么一些镜像:

$ 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
redis               				6.0.8               84c5f6e03bf0        4 weeks ago         104MB
docker                      latest              cf693ec9b5c7        3 weeks ago         105.1 MB
nginx                       latest              e43d811ce2f4        5 weeks ago         181.5 MB

我们可以用镜像的完整 ID,也称为 长 ID,来删除镜像。使用脚本的时候可能会用长 ID,但是人工输入就太累了,所以更多的时候是用 短 ID 来删除镜像。docker image ls 默认列出的就已经是短 ID 了,一般取前3个字符以上,只要足够区分于别的镜像就可以了。

比如这里,如果我们要删除 redis:6.0.8 镜像,可以执行:

ubuntu@VM-0-13-ubuntu:~$ sudo docker image rm 84c
Untagged: redis:6.0.8
Untagged: redis@sha256:1cfb205a988a9dae5f025c57b92e9643ec0e7ccff6e66bc639d8a5f95bba928c
Deleted: sha256:84c5f6e03bf04e139705ceb2612ae274aad94f8dcf8cc630fbf6d91975f2e1c9
Deleted: sha256:302d9c99198ac9f8d56c5dae991b77a64e094626553e3cb601132f35d10ccb61
Deleted: sha256:9c18c8ee5ed59040373f1fe21b7071062be0c7a274331da22d4f29b30716a53e
Deleted: sha256:f8e08eb76c8dbd0c01edda03625e0bd0a3c6521b780374b91d457d4f045aea5e
Deleted: sha256:0df6c826b3b757c0c37e282245c139abc81ba1678a506eb5f8f68174ce2fbee1
Deleted: sha256:04ea7ac0e6e99e0aeec0cd44a9f99f516e2791e0918f35ca58296f664c05676a
Deleted: sha256:07cab433985205f29909739f511777a810f4a9aff486355b71308bb654cdc868

我们也可以用镜像名,也就是 <仓库名>:<标签>,来删除镜像。

$ docker image rm centos
Untagged: centos:latest
Untagged: centos@sha256:b2f9d1c0ff5f87a4743104d099a3d561002ac500db1b9bfa02a783a46e0d366c
Deleted: sha256:0584b3d2cf6d235ee310cf14b54667d889887b838d3f3d3033acd70fc3c48b8a
Deleted: sha256:97ca462ad9eeae25941546209454496e1d66749d53dfa2ee32bf1faabd239d38

当然,更精确的是使用 镜像摘要 删除镜像。

$ docker image ls --digests
REPOSITORY                  TAG                 DIGEST                                                                    IMAGE ID            CREATED             SIZE
node                        slim                sha256:b4f0e0bdeb578043c1ea6862f0d40cc4afe32a4a582f3be235a3b164422be228   6e0c4c8e3913        3 weeks ago         214 MB$ docker image rm node@sha256:b4f0e0bdeb578043c1ea6862f0d40cc4afe32a4a582f3be235a3b164422be228
Untagged: node@sha256:b4f0e0bdeb578043c1ea6862f0d40cc4afe32a4a582f3be235a3b164422be228

Untagged 和 Deleted

如果观察上面这几个命令的运行输出信息的话,你会注意到删除行为分为两类,一类是 Untagged,另一类是 Deleted。我们之前介绍过,镜像的唯一标识是其 ID 和摘要,而一个镜像可以有多个标签。

因此当我们使用上面命令删除镜像的时候,实际上是在要求删除某个标签的镜像。所以首先需要做的是将满足我们要求的所有镜像标签都取消,这就是我们看到的 Untagged 的信息。因为一个镜像可以对应多个标签,因此当我们删除了所指定的标签后,可能还有别的标签指向了这个镜像,如果是这种情况,那么 Delete 行为就不会发生。所以并非所有的 docker rmi 都会产生删除镜像的行为,有可能仅仅是取消了某个标签而已。

当该镜像所有的标签都被取消了,该镜像很可能会失去了存在的意义,因此会触发删除行为。镜像是多层存储结构,因此在删除的时候也是从上层向基础层方向依次进行判断删除。镜像的多层结构让镜像复用变动非常容易,因此很有可能某个其它镜像正依赖于当前镜像的某一层。这种情况,依旧不会触发删除该层的行为。直到没有任何层依赖当前层时,才会真实的删除当前层。这就是为什么,有时候会奇怪,为什么明明没有别的标签指向这个镜像,但是它还是存在的原因,也是为什么有时候会发现所删除的层数和自己 docker pull 看到的层数不一样的源。

除了镜像依赖以外,还需要注意的是容器对镜像的依赖。如果有用这个镜像启动的容器存在(即使容器没有运行),那么同样不可以删除这个镜像。之前讲过,容器是以镜像为基础,再加一层容器存储层,组成这样的多层存储结构去运行的。因此该镜像如果被这个容器所依赖的,那么删除必然会导致故障。如果这些容器是不需要的,应该先将它们删除,然后再来删除镜像。

用 docker image ls 命令来配合

像其它可以承接多个实体的命令一样,可以使用 docker image ls -q 来配合使用 docker image rm,这样可以成批的删除希望删除的镜像。我们在“镜像列表”章节介绍过很多过滤镜像列表的方式都可以拿过来使用。

比如,我们需要删除所有仓库名为 redis 的镜像:

$ docker image rm $(docker image ls -q redis)

或者删除所有在 mongo:3.2 之前的镜像:

$ docker image rm $(docker image ls -q -f before=mongo:3.2)

充分利用你的想象力和 Linux 命令行的强大,你可以完成很多非常赞的功能。

利用 commit 理解镜像构成

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

镜像是容器的基础,每次执行 docker run 的时候都会指定哪个镜像作为容器运行的基础。在之前的例子中,我们所使用的都是来自于 Docker Hub 的镜像。直接使用这些镜像是可以满足一定的需求,而当这些镜像无法直接满足需求时,我们就需要定制这些镜像。接下来的几节就将讲解如何定制镜像。

回顾一下之前我们学到的知识,镜像是多层存储,每一层是在前一层的基础上进行的修改;而容器同样也是多层存储,是在以镜像为基础层,在其基础上加一层作为容器运行时的存储层。

现在让我们以定制一个 Web 服务器为例子,来讲解镜像是如何构建的。

$ docker run --name webserver -d -p 80:80 nginx

这条命令会用 nginx 镜像启动一个容器,命名为 webserver,并且映射了 80 端口,这样我们可以用浏览器去访问这个 nginx 服务器。

如果是在 Linux 本机运行的 Docker,或者如果使用的是 Docker for Mac、Docker for Windows,那么可以直接访问:http://localhost;如果使用的是 Docker Toolbox,或者是在虚拟机、云服务器上安装的 Docker,则需要将 localhost 换为虚拟机地址或者实际云服务器地址。

直接用浏览器访问的话,我们会看到默认的 Nginx 欢迎页面。
在这里插入图片描述现在,假设我们非常不喜欢这个欢迎页面,我们希望改成欢迎 Docker 的文字,我们可以使用 docker exec 命令进入容器,修改其内容。

$ docker exec -it webserver bash
root@3729b97e8226:/# echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
root@3729b97e8226:/# exit
exit

-p(小写p)可以指定要映射的端口,并且在一个指定的端口上只可以绑定一个容器。支持的格式有:IP:HostPort:ContainerPort | IP::ContainerPort | HostPort:ContainerPort 。

我们以交互式终端方式进入 webserver 容器,并执行了 bash 命令,也就是获得一个可操作的 Shell。

然后,我们用 Hello, Docker! 覆盖了 /usr/share/nginx/html/index.html 的内容。

现在我们再刷新浏览器的话,会发现内容被改变了。
在这里插入图片描述我们修改了容器的文件,也就是改动了容器的存储层。我们可以通过 docker diff 命令看到具体的改动。

$ 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

现在我们定制好了变化,我们希望能将其保存下来形成镜像。

要知道,当我们运行一个容器的时候(如果不使用卷的话),我们做的任何文件修改都会被记录于容器存储层里。而 Docker 提供了一个 docker commit 命令,可以将容器的存储层保存下来成为镜像。换句话说,就是在原有镜像的基础上,再叠加上容器的存储层,并构成新的镜像。以后我们运行这个新镜像的时候,就会拥有原有容器最后的文件变化。

docker commit 的语法格式为:

docker commit [选项] <容器ID或容器名> [<仓库名>[:<标签>]]

我们可以用下面的命令将容器保存为镜像:

$ docker commit \--author "lqf <liaoqingfu@gmail.com>" \--message "修改了默认网页" \webserver \nginx:v2
sha256:07e33465974800ce65751acc279adc6ed2dc5ed4e0838f8b86f0c87aa1795214

其中 --author 是指定修改的作者,而 --message 则是记录本次修改的内容。这点和 git 版本控制相似,不过这里这些信息可以省略留空。

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

$ docker image ls nginx
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
nginx               v2                  07e334659748        9 seconds ago       181.5 MB
nginx               1.11                05a60462f8ba        12 days ago         181.5 MB
nginx               latest              e43d811ce2f4        4 weeks ago         181.5 MB```我们还可以用 `docker history` 具体查看镜像内的历史记录,如果比较 `nginx:latest` 的历史记录,我们会发现新增了我们刚刚提交的这一层。```bash
$ docker history nginx:v2
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
07e334659748        54 seconds ago      nginx -g daemon off;                            95 B                修改了默认网页
e43d811ce2f4        4 weeks ago         /bin/sh -c #(nop)  CMD ["nginx" "-g" "daemon    0 B
<missing>           4 weeks ago         /bin/sh -c #(nop)  EXPOSE 443/tcp 80/tcp        0 B
<missing>           4 weeks ago         /bin/sh -c ln -sf /dev/stdout /var/log/nginx/   22 B
<missing>           4 weeks ago         /bin/sh -c apt-key adv --keyserver hkp://pgp.   58.46 MB
<missing>           4 weeks ago         /bin/sh -c #(nop)  ENV NGINX_VERSION=1.11.5-1   0 B
<missing>           4 weeks ago         /bin/sh -c #(nop)  MAINTAINER NGINX Docker Ma   0 B
<missing>           4 weeks ago         /bin/sh -c #(nop)  CMD ["/bin/bash"]            0 B
<missing>           4 weeks ago         /bin/sh -c #(nop) ADD file:23aa4f893e3288698c   123 MB

新的镜像定制好后,我们可以来运行这个镜像。

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

这里我们命名为新的服务为 web2,并且映射到 81 端口。如果是 Docker for Mac/Windows 或 Linux 桌面的话,我们就可以直接访问 http://localhost:81 看到结果,其内容应该和之前修改后的 webserver 一样。

至此,我们第一次完成了定制镜像,使用的是 docker commit 命令,手动操作给旧的镜像添加了新的一层,形成新的镜像,对镜像多层存储应该有了更直观的感觉。

慎用 docker commit

使用 docker commit 命令虽然可以比较直观的帮助理解镜像分层存储的概念,但是实际环境中并不会这样使用。

首先,如果仔细观察之前的 docker diff webserver 的结果,你会发现除了真正想要修改的 /usr/share/nginx/html/index.html 文件外,由于命令的执行,还有很多文件被改动或添加了。这还仅仅是最简单的操作,如果是安装软件包、编译构建,那会有大量的无关内容被添加进来,如果不小心清理,将会导致镜像极为臃肿。

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

而且,回顾之前提及的镜像所使用的分层存储的概念,除当前层外,之前的每一层都是不会发生改变的,换句话说,任何修改的结果仅仅是在当前层进行标记、添加、修改,而不会改动上一层。如果使用 docker commit 制作镜像,以及后期修改的话,每一次修改都会让镜像更加臃肿一次,所删除的上一层的东西并不会丢失,会一直如影随形的跟着这个镜像,即使根本无法访问到。这会让镜像更加臃肿。

实例-使用 Dockerfile 定制镜像

从刚才的 docker commit 的学习中,我们可以了解到,镜像的定制实际上就是定制每一层所添加的配置、文件。如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,那么之前提及的无法重复的问题、镜像构建透明性的问题、体积的问题就都会解决。这个脚本就是 Dockerfile。
Dockerfile 是一个文本文件,其内包含了一条条的指令(Instruction),每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。
还以之前定制 nginx 镜像为例,这次我们使用 Dockerfile 来定制。
在一个空白目录中,建立一个文本文件,并命名为 Dockerfile:

$ mkdir mynginx
$ cd mynginx
$ touch Dockerfile

其内容为:

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

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

然后build镜像

lqf@ubuntu:~/0voice/docker/mynginx$ docker build -t mynginx .
Sending build context to Docker daemon  2.048kB
Step 1/2 : FROM nginx---> 992e3b7be046
Step 2/2 : RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html---> Running in 98b08180b5a7
Removing intermediate container 98b08180b5a7---> d84b89a33f03
Successfully built d84b89a33f03
Successfully tagged mynginx:latest

可以看到新的镜像
在这里插入图片描述

FROM 指定基础镜像

所谓定制镜像,那一定是以一个镜像为基础,在其上进行定制。就像我们之前运行了一个 nginx 镜像的容器,再进行修改一样,基础镜像是必须指定的。而 FROM 就是指定基础镜像,因此一个 Dockerfile 中 FROM 是必备的指令,并且必须是第一条指令。

在 Docker Store 上有非常多的高质量的官方镜像,有可以直接拿来使用的服务类的镜像,如 nginx、redis、mongo、mysql、httpd、php、tomcat 等;也有一些方便开发、构建、运行各种语言应用的镜像,如 node、openjdk、python、ruby、golang 等。可以在其中寻找一个最符合我们最终目标的镜像为基础镜像进行定制。

如果没有找到对应服务的镜像,官方镜像中还提供了一些更为基础的操作系统镜像,如 ubuntu、debian、centos、fedora、alpine 等,这些操作系统的软件库为我们提供了更广阔的扩展空间。

除了选择现有镜像为基础镜像外,Docker 还存在一个特殊的镜像,名为 scratch。这个镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像。

FROM scratch
...

如果你以 scratch 为基础镜像的话,意味着你不以任何镜像为基础,接下来所写的指令将作为镜像第一层开始存在。

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

RUN 执行命令

RUN 指令是用来执行命令行命令的。由于命令行的强大能力,RUN 指令在定制镜像时是最常用的指令之一。其格式有两种:

● shell 格式:RUN <命令>,就像直接在命令行中输入的命令一样。刚才写的 Dockerfile 中的 RUN 指令就是这种格式。

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

● exec 格式:RUN [“可执行文件”, “参数1”, “参数2”],这更像是函数调用中的格式。

既然 RUN 就像 Shell 脚本一样可以执行命令,那么我们是否就可以像 Shell 脚本一样把每个命令对应一个 RUN 呢?比如这样:

FROM debian:jessieRUN apt-get update
RUN apt-get install -y gcc libc6-dev make
RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-3.2.5.tar.gz"
RUN mkdir -p /usr/src/redis
RUN tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1
RUN make -C /usr/src/redis
RUN make -C /usr/src/redis install

之前说过,Dockerfile 中每一个指令都会建立一层,RUN 也不例外。每一个 RUN 的行为,就和刚才我们手工建立镜像的过程一样:新建立一层,在其上执行这些命令,执行结束后,commit 这一层的修改,构成新的镜像。

而上面的这种写法,创建了 7 层镜像。这是完全没有意义的,而且很多运行时不需要的东西,都被装进了镜像里,比如编译环境、更新的软件包等等。结果就是产生非常臃肿、非常多层的镜像,不仅仅增加了构建部署的时间,也很容易出错。
这是很多初学 Docker 的人常犯的一个错误。

Union FS 是有最大层数限制的,比如 AUFS,曾经是最大不得超过 42 层,现在是不得超过 127 层。

上面的 Dockerfile 正确的写法应该是这样:

FROM debian:jessieRUN buildDeps='gcc libc6-dev make' \&& apt-get update \&& apt-get install -y $buildDeps \&& wget -O redis.tar.gz "http://download.redis.io/releases/redis-3.2.5.tar.gz" \&& mkdir -p /usr/src/redis \&& tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 \&& make -C /usr/src/redis \&& make -C /usr/src/redis install \&& rm -rf /var/lib/apt/lists/* \&& rm redis.tar.gz \&& rm -r /usr/src/redis \&& apt-get purge -y --auto-remove $buildDeps

首先,之前所有的命令只有一个目的,就是编译、安装 redis 可执行文件。因此没有必要建立很多层,这只是一层的事情。因此,这里没有使用很多个 RUN 对一一对应不同的命令,而是仅仅使用一个 RUN 指令,并使用 && 将各个所需命令串联起来。将之前的 7 层,简化为了 1 层。在撰写 Dockerfile 的时候,要经常提醒自己,这并不是在写 Shell 脚本,而是在定义每一层该如何构建。

并且,这里为了格式化还进行了换行。Dockerfile 支持 Shell 类的行尾添加 \ 的命令换行方式,以及行首 # 进行注释的格式。良好的格式,比如换行、缩进、注释等,会让维护、排障更为容易,这是一个比较好的习惯。

此外,还可以看到这一组命令的最后添加了清理工作的命令,删除了为了编译构建所需要的软件,清理了所有下载、展开的文件,并且还清理了 apt 缓存文件。这是很重要的一步,我们之前说过,镜像是多层存储,每一层的东西并不会在下一层被删除,会一直跟随着镜像。因此镜像构建时,一定要确保每一层只添加真正需要添加的东西,任何无关的东西都应该清理掉。

很多人初学 Docker 制作出了很臃肿的镜像的原因之一,就是忘记了每一层构建的最后一定要清理掉无关文件。

构建镜像

好了,让我们再回到之前定制的 nginx 镜像的 Dockerfile 来。现在我们明白了这个 Dockerfile 的内容,那么让我们来构建这个镜像吧。

在 Dockerfile 文件所在目录执行:

$ docker build -t nginx:v3 .
Sending build context to Docker daemon 2.048 kB
Step 1 : FROM nginx---> e43d811ce2f4
Step 2 : RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html---> Running in 9cdc27646c7b---> 44aa4490ce2c
Removing intermediate container 9cdc27646c7b
Successfully built 44aa4490ce2c

从命令的输出结果中,我们可以清晰的看到镜像的构建过程。在 Step 2 中,如同我们之前所说的那样,
RUN 指令启动了一个容器 9cdc27646c7b,执行了所要求的命令,并最后提交了这一层 44aa4490ce2c,随后删除了所用到的这个容器 9cdc27646c7b。

这里我们使用了 docker build 命令进行镜像构建。其格式为:

docker build [选项] <上下文路径/URL/->

在这里我们指定了最终镜像的名称 -t nginx:v3,构建成功后,我们可以像之前运行 nginx:v2 那样来运行这个镜像,其结果会和 nginx:v2 一样。

镜像构建上下文(Context)

如果注意,会看到 docker build 命令最后有一个 .。. 表示当前目录,而 Dockerfile 就在当前目录,因此不少初学者以为这个路径是在指定 Dockerfile 所在路径,这么理解其实是不准确的。如果对应上面的命令格式,你可能会发现,这是在指定上下文路径。那么什么是上下文呢?

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

当我们进行镜像构建的时候,并非所有定制都会通过 RUN 指令完成,经常会需要将一些本地文件复制进镜像,比如通过 COPY 指令、ADD 指令等。而 docker build 命令构建镜像,其实并非在本地构建,而是在服务端,也就是 Docker 引擎中构建的。那么在这种客户端/服务端的架构中,如何才能让服务端获得本地文件呢?

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

如果在 Dockerfile 中这么写:

COPY ./package.json /app/

这并不是要复制执行 docker build 命令所在的目录下的 package.json,也不是复制 Dockerfile 所在目录下的 package.json,而是复制 上下文(context) 目录下的 package.json。

因此,COPY 这类指令中的源文件的路径都是相对路径。这也是初学者经常会问的为什么 COPY …/package.json /app 或者 COPY /opt/xxxx /app 无法工作的原因,因为这些路径已经超出了上下文的范围,Docker 引擎无法获得这些位置的文件。如果真的需要那些文件,应该将它们复制到上下文目录中去。

现在就可以理解刚才的命令 docker build -t nginx:v3 . 中的这个 .,实际上是在指定上下文的目录,docker build 命令会将该目录下的内容打包交给 Docker 引擎以帮助构建镜像。

如果观察 docker build 输出,我们其实已经看到了这个发送上下文的过程:

$ docker build -t nginx:v3 .
Sending build context to Docker daemon 2.048 kB
...

理解构建上下文对于镜像构建是很重要的,避免犯一些不应该的错误。比如有些初学者在发现 COPY /opt/xxxx /app 不工作后,于是干脆将 Dockerfile 放到了硬盘根目录去构建,结果发现 docker build 执行后,在发送一个几十 GB 的东西,极为缓慢而且很容易构建失败。那是因为这种做法是在让 docker build 打包整个硬盘,这显然是使用错误。

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

那么为什么会有人误以为 . 是指定 Dockerfile 所在目录呢?这是因为在默认情况下,如果不额外指定 Dockerfile 的话,会将上下文目录下的名为 Dockerfile 的文件作为 Dockerfile。

这只是默认行为,实际上 Dockerfile 的文件名并不要求必须为 Dockerfile,而且并不要求必须位于上下文目录中,比如可以用 -f …/Dockerfile.php 参数指定某个文件作为 Dockerfile。

当然,一般大家习惯性的会使用默认的文件名 Dockerfile,以及会将其置于镜像构建上下文目录中。

其它 docker build 的用法

直接用 Git repo 进行构建
或许你已经注意到了,docker build 还支持从 URL 构建,比如可以直接从 Git repo 中构建:

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

这行命令指定了构建所需的 Git repo,并且指定默认的 master 分支,构建目录为 /8.14/,然后 Docker 就会自己去 git clone 这个项目、切换到指定分支、并进入到指定目录后开始构建。
用给定的 tar 压缩包构建

$ docker build http://server/context.tar.gz

如果所给出的 URL 不是个 Git repo,而是个 tar 压缩包,那么 Docker 引擎会下载这个包,并自动解压缩,以其作为上下文,开始构建。
从标准输入中读取 Dockerfile 进行构建

docker build - < Dockerfile

cat Dockerfile | docker build -

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

$ docker build - < context.tar.gz

如果发现标准输入的文件格式是 gzip、bzip2 以及 xz 的话,将会使其为上下文压缩包,直接将其展开,将里面视为上下文,并开始构建。

Dockerfile 指令详解

我们已经介绍了 FROM,RUN,还提及了 COPY, ADD,其实 Dockerfile 功能很强大,它提供了十多个指令。下面我们继续讲解其他的指令。
Dockerfile由多条指令组成,每条指令在编译镜像时执行相应的程序完成某些功能,由指令+参数组成,以逗号分隔,#作为注释起始符,虽说指令不区分大小写,但是一般指令使用大些,参数使用小写
在这里插入图片描述

指令:FROM
功能描述:设置基础镜像
语法:FROM < image>[:< tag> | @< digest>]
提示:镜像都是从一个基础镜像(操作系统或其他镜像)生成,可以在一个Dockerfile中添加多条FROM指令,一次生成多个镜像
注意:如果忽略tag选项,会使用latest镜像
MAINTAINER
指令:MAINTAINER
功能描述:设置镜像作者
语法:MAINTAINER < name>

RUN
指令:RUN
功能描述:
语法:RUN < command>
RUN [“executable”,”param1”,”param2”]
提示:RUN指令会生成容器,在容器中执行脚本,容器使用当前镜像,脚本指令完成后,Docker Daemon会将该容器提交为一个中间镜像,供后面的指令使用
补充:RUN指令第一种方式为shell方式,使用/bin/sh -c < command>运行脚本,可以在其中使用\将脚本分为多行
RUN指令第二种方式为exec方式,镜像中没有/bin/sh或者要使用其他shell时使用该方式,其不会调用shell命令
例子:RUN source $HOME/.bashrc;\
echo HOMERUN[“/bin/bash”,”−c”,”echohello”]RUN[“sh”,”−c”,”echo”,”HOME RUN [“/bin/bash”,”-c”,”echo hello”] RUN [“sh”,”-c”,”echo”,”HOMERUN[/bin/bash,c,echohello]RUN[sh,c,echo,HOME”] 使用第二种方式调用shell读取环境变量
CMD 容器启动命令
指令:CMD
功能描述:设置容器的启动命令
语法:CMD [“executable”,”param1”,”param2”]
CMD [“param1”,”param2”]
CMD < command>
提示:CMD第一种、第三种方式和RUN类似,第二种方式为ENTRYPOINT参数方式,为entrypoint提供参数列表
注意:Dockerfile中只能有一条CMD命令,如果写了多条则最后一条生效

LABEL 容器标签
指令:LABEL
功能描述:设置镜像的标签
延伸:镜像标签可以通过docker inspect查看
格式:LABEL < key>=< value> < key>=< value> …
提示:不同标签之间通过空格隔开
注意:每条指令都会生成一个镜像层,Docker中镜像最多只能有127层,如果超出Docker Daemon就会报错,如LABEL …=… <假装这里有个换行> LABEL …=…合在一起用空格分隔就可以减少镜像层数量,同样,可以使用连接符\将脚本分为多行
镜像会继承基础镜像中的标签,如果存在同名标签则会覆盖

EXPOSE 暴露端口
指令:EXPOSE
功能描述:设置镜像暴露端口,记录容器启动时监听哪些端口
语法:EXPOSE < port> < port> …
延伸:镜像暴露端口可以通过docker inspect查看
提示:容器启动时,Docker Daemon会扫描镜像中暴露的端口,如果加入-P参数,Docker Daemon会把镜像中所有暴露端口导出,并为每个暴露端口分配一个随机的主机端口(暴露端口是容器监听端口,主机端口为外部访问容器的端口)
注意:EXPOSE只设置暴露端口并不导出端口,只有启动容器时使用-P/-p才导出端口,这个时候才能通过外部访问容器提供的服务

ENV 设置环境变量
指令:ENV
功能描述:设置镜像中的环境变量
语法:ENV < key>=< value>…|< key> < value>
注意:环境变量在整个编译周期都有效,第一种方式可设置多个环境变量,第二种方式只设置一个环境变量
提示:通过${变量名}或者 变量名使用变量,使用方式变量名使用变量,使用方式使使{变量名}时可以用${变量名:-default} ${变量名:+cover}设定默认值或者覆盖值
ENV设置的变量值在整个编译过程中总是保持不变的

ADD 更高级的复制文件
指令:ADD
功能描述:复制文件到镜像中
语法:ADD < src>… < dest>|[“< src>”,… “< dest>”]
注意:当路径中有空格时,需要使用第二种方式
当src为文件或目录时,Docker Daemon会从编译目录寻找这些文件或目录,而dest为镜像中的绝对路径或者相对于WORKDIR的路径
提示:src为目录时,复制目录中所有内容,包括文件系统的元数据,但不包括目录本身
src为压缩文件,并且压缩方式为gzip,bzip2或xz时,指令会将其解压为目录
如果src为文件,则复制文件和元数据
如果dest不存在,指令会自动创建dest和缺失的上级目录

COPY复制文件
指令:COPY
功能描述:复制文件到镜像中
语法:COPY < src>… < dest>|[“< src>”,… “< dest>”]
提示:指令逻辑和ADD十分相似,同样Docker Daemon会从编译目录寻找文件或目录,dest为镜像中的绝对路径或者相对于WORKDIR的路径

ENTRYPOINT 入口点
指令:ENTRYPOINT
功能描述:设置容器的入口程序
语法:ENTRYPOINT [“executable”,”param1”,”param2”]
ENTRYPOINT command param1 param2(shell方式)
提示:入口程序是容器启动时执行的程序,docker run中最后的命令将作为参数传递给入口程序
入口程序有两种格式:exec、shell,其中shell使用/bin/sh -c运行入口程序,此时入口程序不能接收信号量
当Dockerfile有多条ENTRYPOINT时只有最后的ENTRYPOINT指令生效
如果使用脚本作为入口程序,需要保证脚本的最后一个程序能够接收信号量,可以在脚本最后使用exec或gosu启动传入脚本的命令
注意:通过shell方式启动入口程序时,会忽略CMD指令和docker run中的参数
为了保证容器能够接受docker stop发送的信号量,需要通过exec启动程序;如果没有加入exec命令,则在启动容器时容器会出现两个进程,并且使用docker stop命令容器无法正常退出(无法接受SIGTERM信号),超时后docker stop发送SIGKILL,强制停止容器
例子:FROM ubuntu <换行> ENTRYPOINT exec top -b

VOLUME 定义匿名卷
指令:VOLUME
功能描述:设置容器的挂载点
语法:VOLUME [“/data”]
VOLUME /data1 /data2
提示:启动容器时,Docker Daemon会新建挂载点,并用镜像中的数据初始化挂载点,可以将主机目录或数据卷容器挂载到这些挂载点

USER 指定当前用户
指令:USER
功能描述:设置RUN CMD ENTRYPOINT的用户名或UID
语法:USER < name>

WORKDIR 指定工作目录
指令:WORKDIR
功能描述:设置RUN CMD ENTRYPOINT ADD COPY指令的工作目录
语法:WORKDIR < Path>
提示:如果工作目录不存在,则Docker Daemon会自动创建
Dockerfile中多个地方都可以调用WORKDIR,如果后面跟的是相对位置,则会跟在上条WORKDIR指定路径后(如WORKDIR /A WORKDIR B WORKDIR C,最终路径为/A/B/C)

ARG 构建参数
指令:ARG
功能描述:设置编译变量
语法:ARG < name>[=< defaultValue>]
注意:ARG从定义它的地方开始生效而不是调用的地方,在ARG之前调用编译变量总为空,在编译镜像时,可以通过docker build –build-arg < var>=< value>设置变量,如果var没有通过ARG定义则Daemon会报错
可以使用ENV或ARG设置RUN使用的变量,如果同名则ENV定义的值会覆盖ARG定义的值,与ENV不同,ARG的变量值在编译过程中是可变的,会对比使用编译缓存造成影响(ARG值不同则编译过程也不同)
例子:ARG CONT_IMAG_VER <换行> RUN echo $CONT_IMG_VER
ARG CONT_IMAG_VER <换行> RUN echo hello
当编译时给ARG变量赋值hello,则两个Dockerfile可以使用相同的中间镜像,如果不为hello,则不能使用同一个中间镜像
ONBUILD 为他人做嫁衣
指令:ONBUILD
功能描述:设置自径想的编译钩子指令
语法:ONBUILD [INSTRUCTION]
提示:从该镜像生成子镜像,在子镜像的编译过程中,首先会执行父镜像中的ONBUILD指令,所有编译指令都可以成为钩子指令

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

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

假设我们要制作 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 放到 Node.js 项目的根目录,构建好镜像后,就可以直接拿来启动容器运行。但是如果我们还有第二个 Node.js 项目也差不多呢?好吧,那就再把这个 Dockerfile 复制到第二个项目里。那如果有第三个项目呢?再复制么?文件的副本越多,版本控制就越困难,让我们继续看这样的场景维护的问题。

如果第一个 Node.js 项目在开发过程中,发现这个 Dockerfile 里存在问题,比如敲错字了、或者需要安装额外的包,然后开发人员修复了这个 Dockerfile,再次构建,问题解决。第一个项目没问题了,但是第二个项目呢?虽然最初 Dockerfile 是复制、粘贴自第一个项目的,但是并不会因为第一个项目修复了他们的 Dockerfile,而第二个项目的 Dockerfile 就会被自动修复。

那么我们可不可以做一个基础镜像,然后各个项目使用这个基础镜像呢?这样基础镜像更新,各个项目不用同步 Dockerfile 的变化,重新构建后就继承了基础镜像的更新?好吧,可以,让我们看看这样的结果。那么上面的这个 Dockerfile 就会变为:

FROM node:slim
RUN mkdir /app
WORKDIR /app
CMD [ "npm", "start" ]

这里我们把项目相关的构建指令拿出来,放到子项目里去。假设这个基础镜像的名字为 my-node 的话,各个项目内的自己的 Dockerfile 就变为:

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

基础镜像变化后,各个项目都用这个 Dockerfile 重新构建镜像,会继承基础镜像的更新。

那么,问题解决了么?没有。准确说,只解决了一半。如果这个 Dockerfile 里面有些东西需要调整呢?比如 npm install 都需要加一些参数,那怎么办?这一行 RUN 是不可能放入基础镜像的,因为涉及到了当前项目的 ./package.json,难道又要一个个修改么?所以说,这样制作基础镜像,只解决了原来的 Dockerfile 的前4条指令的变化问题,而后面三条指令的变化则完全没办法处理。

ONBUILD 可以解决这个问题。让我们用 ONBUILD 重新写一下基础镜像的 Dockerfile:

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

这次我们回到原始的 Dockerfile,但是这次将项目相关的指令加上 ONBUILD,这样在构建基础镜像的时候,这三行并不会被执行。然后各个项目的 Dockerfile 就变成了简单地:

FROM my-node

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

STOPSIGNAL 停止信号
指令:STOPSIGNAL
功能描述:设置容器退出时,Docker Daemon向容器发送的信号量
语法:STOPSIGNAL signal
提示:信号量可以是数字或者信号量的名字,如9或者SIGKILL,信号量的数字说明在Linux系统管理中有简单介绍

补充:ONBUILD流程
● 编译时,读取所有ONBUILD镜像并记录下来,在当前编译过程中不执行指令
● 生成镜像时将所有ONBUILD指令记录在镜像的配置文件OnBuild关键字中
● 子镜像在执行FROM指令时会读取基础镜像中的ONBUILD指令并顺序执行,如果执行过程中失败则编译中断;当所有ONBUILD执行成功后开始执行子镜像中的指令
● 子镜像不会继承基础镜像中的ONBUILD指令

操作Docker容器

容器是 Docker 三大核心概念之一。如果说Docker镜像是类,则Docker容器就是实例化后的对象,同一个镜像可以启动多个容器。

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

本章将具体介绍如何来管理一个容器,包括创建、启动和停止等。

启动容器

启动容器有两种方式,一种是基于镜像新建一个容器并启动,另外一个是将在终止状态(stopped)的容器重新启动。

因为 Docker 的容器实在太轻量级了,很多时候用户都是随时删除和新创建容器。

新建并启动

所需要的命令主要为 docker run。

先拉取ubuntu:16.04
docker pull ubuntu:16.04

ubuntu@VM-0-13-ubuntu:~$ sudo docker pull ubuntu:16.04
16.04: Pulling from library/ubuntu
Digest: sha256:185fec2d6dbe9165f35e4a1136b4cf09363b328d4f850695393ca191aa1475fd
Status: Image is up to date for ubuntu:16.04
docker.io/library/ubuntu:16.04

例如,下面的命令输出一个 “Hello World”,之后终止容器。

ubuntu@VM-0-13-ubuntu:~$ sudo docker run ubuntu:16.04 /bin/echo 'Hello world'
Hello world

这跟在本地直接执行 /bin/echo ‘hello world’ 几乎感觉不出任何区别。

下面的命令则启动一个 bash 终端,允许用户进行交互。

ubuntu@VM-0-13-ubuntu:~$ sudo docker run -t -i ubuntu:16.04 /bin/bash
root@b1d435526f3d:/# 

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

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

root@b1d435526f3d:/# ls
bin   dev  home  lib64  mnt  proc  run   srv  tmp  var
boot  etc  lib   media  opt  root  sbin  sys  usr
root@b1d435526f3d:/# cd home/
root@b1d435526f3d:/home# ls
root@b1d435526f3d:/home# pwd
/home

当利用 docker run 来创建容器时,Docker 在后台运行的标准操作包括:

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

启动已终止容器

可以利用 docker container start 命令,直接将一个已经终止的容器启动运行。

容器的核心为所执行的应用程序,所需要的资源都是应用程序运行所必需的。除此之外,并没有其它的资源。可以在伪终端中利用 ps 或 top 来查看进程信息。

root@b1d435526f3d:~# psPID TTY          TIME CMD1 pts/0    00:00:00 bash13 pts/0    00:00:00 ps

可见,容器中仅运行了指定的 bash 应用。这种特点使得 Docker 对资源的利用率极高,是货真价实的轻量级虚拟化。

后台运行

更多的时候,需要让 Docker 在后台运行而不是直接把执行命令的结果输出在当前宿主机下。此时,可以通过添加 -d 参数来实现。

下面举两个例子来说明一下。

如果不使用 -d 参数运行容器。

ubuntu@VM-0-13-ubuntu:~$ sudo docker run ubuntu:16.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"
hello world
hello world
hello world

容器会把输出的结果 (STDOUT) 打印到宿主机上面

如果使用了 -d 参数运行容器。

ubuntu@VM-0-13-ubuntu:~$ sudo docker run -d ubuntu:16.04 /bin/sh -c "while true; do echo hellworld; sleep 1; done"
fb1dce2083d220be710231b9d40136394d8ce3f4222a8c689d1713810a2fe690

此时容器会在后台运行并不会把输出的结果 (STDOUT) 打印到宿主机上面(输出结果可以用 docker logs 查看)。

注: 容器是否会长久运行,是和 docker run 指定的命令有关,和 -d 参数无关。

使用 -d 参数启动后会返回一个唯一的 id (CONTAINER ID),也可以通过 docker container ls 命令来查看容器信息。

$ docker container ls
CONTAINER ID  IMAGE         COMMAND               CREATED        STATUS       PORTS NAMES
77b2dc01fe0f  ubuntu:17.10  /bin/sh -c 'while tr  2 minutes ago  Up 1 minute        agitated_wright

要获取容器的输出信息,可以通过 docker container logs 命令。
格式:docker container logs [container ID or NAMES]

ubuntu@VM-0-13-ubuntu:~$ sudo docker logs fb1dce2083d220be710231b9d40136394d8ce3f4222a8c689d1713810a2fe690
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world

终止容器

可以使用 docker container stop 来终止一个运行中的容器。

此外,当 Docker 容器中指定的应用终结时,容器也自动终止。

例如对于上一章节中只启动了一个终端的容器,用户通过 exit 命令或 Ctrl+d 来退出终端时,所创建的容器立刻终止。
注意:容器不退出,返回宿主机 则使用Ctrl+P+Q

终止状态的容器可以用 docker container ls -a 命令看到。例如

ubuntu@VM-0-13-ubuntu:~$ sudo docker container ls -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                      PORTS               NAMES
fb1dce2083d2        ubuntu:16.04        "/bin/sh -c 'while t…"   2 minutes ago       Up 2 minutes                                    ecstatic_wu
aa8a29338b15        ubuntu:16.04        "/bin/sh -c 'while t…"   3 minutes ago       Exited (0) 2 minutes ago                        crazy_nobel
d82043060122        ubuntu:16.04        "/bin/bash"              3 minutes ago       Up 3 minutes                                    cool_mendeleev
b1d435526f3d        ubuntu:16.04        "/bin/bash"              9 minutes ago       Exited (0) 3 minutes ago                        dreamy_jackson
0c404fb5a7b5        ubuntu:16.04        "/bin/echo 'Hello wo…"   10 minutes ago      Exited (0) 10 minutes ago                       xenodochial_herschel

停止fb1dce2083d2这个容器

ubuntu@VM-0-13-ubuntu:~$ sudo docker container stop fb1dce2083d2
fb1dce2083d2

处于终止状态的容器,可以通过 docker container start 命令来重新启动。

此外,docker container restart 命令会将一个运行态的容器终止,然后再重新启动它。

进入容器

在使用 -d 参数时,容器启动后会进入后台。

某些时候需要进入容器进行操作,包括使用 docker attach 命令或 docker exec 命令,推荐大家使用 docker exec 命令,原因会在下面说明。

**attach 命令 **

docker attach 是 Docker 自带的命令。下面示例如何使用该命令。

$ docker run -dit ubuntu
243c32535da7d142fb0e6df616a3c3ada0b8ab417937c853a9e1c251f499f550$ docker container ls
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
243c32535da7        ubuntu:16.04       "/bin/bash"         18 seconds ago      Up 17 seconds                           nostalgic_hypatia$ docker attach 243c
root@243c32535da7:/#

注意: 如果从这个 stdin 中 exit,会导致容器的停止。

exec 命令
-i -t 参数
docker exec 后边可以跟多个参数,这里主要说明 -i -t 参数。

只用 -i 参数时,由于没有分配伪终端,界面没有我们熟悉的 Linux 命令提示符,但命令执行结果仍然可以返回。

当 -i -t 参数一起使用时,则可以看到我们熟悉的 Linux 命令提示符。

$ docker run -dit ubuntu
69d137adef7a8a689cbcb059e94da5489d3cddd240ff675c640c8d96e84fe1f6$ docker container ls
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
69d137adef7a        ubuntu:latest       "/bin/bash"         18 seconds ago      Up 17 seconds                           zealous_swirles$ docker exec -i 69d1 bash
ls
bin
boot
dev
...$ docker exec -it 69d1 bash
root@69d137adef7a:/#

如果从这个 stdin 中 exit,不会导致容器的停止。这就是为什么推荐大家使用 docker exec 的原因。

更多参数说明请使用 docker exec --help 查看。

导出和导入容器

导出容器

如果要导出本地某个容器,可以使用 docker export 命令。

$ docker container ls -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                    PORTS               NAMES
7691a814370e        ubuntu:16.04        "/bin/bash"         36 hours ago        Exited (0) 21 hours ago                       test
$ docker export 7691a814370e > ubuntu.tar

这样将导出容器快照到本地文件。

导入容器快照

可以使用 docker import 从容器快照文件中再导入为镜像,例如

$ cat ubuntu.tar | docker import - test/ubuntu:v1.0
$ docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED              VIRTUAL SIZE
test/ubuntu         v1.0                9d37a6082e97        About a minute ago   171.3 MB

此外,也可以通过指定 URL 或者某个目录来导入,例如

$ docker import http://example.com/exampleimage.tgz example/imagerepo

注:用户既可以使用 docker load 来导入镜像存储文件到本地镜像库,也可以使用 docker import 来导入一个容器快照到本地镜像库。这两者的区别在于容器快照文件将丢弃所有的历史记录和元数据信息(即仅保存容器当时的快照状态),而镜像存储文件将保存完整记录,体积也要大。此外,从容器快照文件导入时可以重新指定标签等元数据信息。

操作仓库

仓库(Repository)是集中存放镜像的地方。

一个容易混淆的概念是注册服务器(Registry)。实际上注册服务器是管理仓库的具体服务器,每个服务器上可以有多个仓库,而每个仓库下面有多个镜像。从这方面来说,仓库可以被认为是一个具体的项目或目录。例如对于仓库地址 dl.dockerpool.com/ubuntu 来说,dl.dockerpool.com 是注册服务器地址,ubuntu 是仓库名。

大部分时候,并不需要严格区分这两者的概念。

Docker Hub

目前 Docker 官方维护了一个公共仓库 Docker Hub,其中已经包括了数量超过 15,000 的镜像。大部分需求都可以通过在 Docker Hub 中直接下载镜像来实现。

注册

你可以在 https://cloud.docker.com 免费注册一个 Docker 账号。

登录

可以通过执行 docker login 命令交互式的输入用户名及密码来完成在命令行界面登录 Docker Hub。

你可以通过 docker logout 退出登录。

拉取镜像

你可以通过 docker search 命令来查找官方仓库中的镜像,并利用 docker pull 命令来将它下载到本地。

例如以 centos 为关键词进行搜索:

$ docker search centos
NAME                               DESCRIPTION                                     STARS               OFFICIAL            AUTOMATED
centos                             The official build of CentOS.                   6232                [OK]                
ansible/centos7-ansible            Ansible on Centos7                              132                                     [OK]
consol/centos-xfce-vnc             Centos container with "headless" VNC session…   122                                     [OK]
jdeathe/centos-ssh                 OpenSSH / Supervisor / EPEL/IUS/SCL Repos - …   115                                     [OK]
centos/systemd                     systemd enabled base container.                 86                                      [OK]
centos/mysql-57-centos7            MySQL 5.7 SQL database server                   83                                      
imagine10255/centos6-lnmp-php56    centos6-lnmp-php56                              58                                      [OK]
tutum/centos                       Simple CentOS docker image with SSH access      46                                      
centos/postgresql-96-centos7       PostgreSQL is an advanced Object-Relational …   46                                      
kinogmt/centos-ssh                 CentOS with SSH                                 29                                      [OK]
pivotaldata/centos-gpdb-dev        CentOS image for GPDB development. Tag names…   13                                      
guyton/centos6                     From official centos6 container with full up…   10                                      [OK]
centos/tools                       Docker image that has systems administration…   6                                       [OK]
drecom/centos-ruby                 centos ruby                                     6                                       [OK]
pivotaldata/centos                 Base centos, freshened up a little with a Do…   5                                       
darksheer/centos                   Base Centos Image -- Updated hourly             3                                       [OK]
mamohr/centos-java                 Oracle Java 8 Docker image based on Centos 7    3                                       [OK]
pivotaldata/centos-gcc-toolchain   CentOS with a toolchain, but unaffiliated wi…   3                                       
pivotaldata/centos-mingw           Using the mingw toolchain to cross-compile t…   3                                       
indigo/centos-maven                Vanilla CentOS 7 with Oracle Java Developmen…   1                                       [OK]
blacklabelops/centos               CentOS Base Image! Built and Updates Daily!     1                                       [OK]
mcnaughton/centos-base             centos base image                               1                                       [OK]
pivotaldata/centos6.8-dev          CentosOS 6.8 image for GPDB development         0                                       
pivotaldata/centos7-dev            CentosOS 7 image for GPDB development           0                                       
smartentry/centos                  centos with smartentry                          0                                       [OK]

可以看到返回了很多包含关键字的镜像,其中包括镜像名字、描述、收藏数(表示该镜像的受关注程度)、是否官方创建、是否自动创建。

官方的镜像说明是官方项目组创建和维护的,automated 资源允许用户验证镜像的来源和内容。

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

一种是类似 centos 这样的镜像,被称为基础镜像或根镜像。这些基础镜像由 Docker 公司创建、验证、支持、提供。这样的镜像往往使用单个单词作为名字。

还有一种类型,比如 tianon/centos 镜像,它是由 Docker 的用户创建并维护的,往往带有用户名称前缀。可以通过前缀 username/ 来指定使用某个用户提供的镜像,比如 tianon 用户。

另外,在查找的时候通过 --filter=stars=N 参数可以指定仅显示收藏数量为 N 以上的镜像。

下载官方 centos 镜像到本地。

$ docker pull centos
Pulling repository centos
0b443ba03958: Download complete
539c0211cd76: Download complete
511136ea3c5a: Download complete
7064731afe90: Download complete

推送镜像

用户也可以在登录后通过 docker push 命令来将自己的镜像推送到 Docker Hub。

以下命令中的 username 请替换为你的 Docker 账号用户名。比如liaoqingfu

$ docker tag ubuntu:16.04 liaoqingfu/ubuntu:16.04$ docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
liaoqingfu/ubuntu   16.04               096efd74bb89        2 weeks ago         127MB
ubuntu              16.04               096efd74bb89        2 weeks ago         127MB$ docker push  liaoqingfu/ubuntu$ docker search liaoqingfuNAME                      DESCRIPTION                                     STARS               OFFICIAL            AUTOMATED
liaoqingfu/ubuntu:16.04

自动创建

自动创建(Automated Builds)功能对于需要经常升级镜像内程序来说,十分方便。

有时候,用户创建了镜像,安装了某个软件,如果软件发布新版本则需要手动更新镜像。

而自动创建允许用户通过 Docker Hub 指定跟踪一个目标网站(目前支持 GitHub 或 BitBucket)上的项目,一旦项目发生新的提交或者创建新的标签(tag),Docker Hub 会自动构建镜像并推送到 Docker Hub 中。

要配置自动创建,包括如下的步骤:

● 创建并登录 Docker Hub,以及目标网站;
● 在目标网站中连接帐户到 Docker Hub;
● 在 Docker Hub 中 配置一个自动创建;
● 选取一个目标网站中的项目(需要含 Dockerfile)和分支;
● 指定 Dockerfile 的位置,并提交创建。

之后,可以在 Docker Hub 的 自动创建页面 中跟踪每次创建的状态。

私有仓库

有时候使用 Docker Hub 这样的公共仓库可能不方便,用户可以创建一个本地仓库供私人使用。

本节介绍如何使用本地仓库。

docker-registry 是官方提供的工具,可以用于构建私有的镜像仓库。本文内容基于 docker-registry v2.x 版本。

安装运行 docker-registry

容器运行

你可以通过获取官方 registry 镜像来运行。

$ docker run -d -p 5000:5000 --restart=always --name registry registry

这将使用官方的 registry 镜像来启动私有仓库。默认情况下,仓库会被创建在容器的 /var/lib/registry 目录下。你可以通过 -v 参数来将镜像文件存放在本地的指定路径。例如下面的例子将上传的镜像放到本地的 /opt/data/registry 目录。

$ docker run -d \-p 5000:5000 \-v /opt/data/registry:/var/lib/registry \registry

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

创建好私有仓库之后,就可以使用 docker tag 来标记一个镜像,然后推送它到仓库。例如私有仓库地址为 127.0.0.1:5000。

先在本机查看已有的镜像。

$ docker image ls
REPOSITORY                        TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
ubuntu                            latest              ba5877dc9bec        6 weeks ago         192.7 MB

使用 docker tag 将 ubuntu:latest 这个镜像标记为 127.0.0.1:5000/ubuntu:latest。

格式为 docker tag IMAGE[:TAG] [REGISTRY_HOST[:REGISTRY_PORT]/]REPOSITORY[:TAG]。

$ docker tag ubuntu:latest 127.0.0.1:5000/ubuntu:latest
$ docker image ls
REPOSITORY                        TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
ubuntu                            latest              ba5877dc9bec        6 weeks ago         192.7 MB
127.0.0.1:5000/ubuntu:latest      latest              ba5877dc9bec        6 weeks ago         192.7 MB

使用 docker push 上传标记的镜像。

$ docker push 127.0.0.1:5000/ubuntu:latest
The push refers to repository [127.0.0.1:5000/ubuntu]
373a30c24545: Pushed
a9148f5200b0: Pushed
cdd3de0940ab: Pushed
fc56279bbb33: Pushed
b38367233d37: Pushed
2aebd096e0e2: Pushed
latest: digest: sha256:fe4277621f10b5026266932ddf760f5a756d2facd505a94d2da12f4f52f71f5a size: 1568

用 curl 查看仓库中的镜像。

$ curl 127.0.0.1:5000/v2/_catalog
{"repositories":["ubuntu"]}

这里可以看到 {“repositories”:[“ubuntu”]},表明镜像已经被成功上传了。

先删除已有镜像,再尝试从私有仓库中下载这个镜像。

$ docker image rm 127.0.0.1:5000/ubuntu:latest$ docker pull 127.0.0.1:5000/ubuntu:latest
Pulling repository 127.0.0.1:5000/ubuntu:latest
ba5877dc9bec: Download complete
511136ea3c5a: Download complete
9bad880da3d2: Download complete
25f11f5fb0cb: Download complete
ebc34468f71d: Download complete
2318d26665ef: Download complete$ docker image ls
REPOSITORY                         TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
127.0.0.1:5000/ubuntu:latest       latest              ba5877dc9bec        6 weeks ago         192.7 MB

注意事项

如果你不想使用 127.0.0.1:5000 作为仓库地址,比如想让本网段的其他主机也能把镜像推送到私有仓库。你就得把例如 192.168.199.100:5000 这样的内网地址作为私有仓库地址,这时你会发现无法成功推送镜像。

这是因为 Docker 默认不允许非 HTTPS 方式推送镜像。我们可以通过 Docker 的配置选项来取消这个限制,或者查看下一节配置能够通过 HTTPS 访问的私有仓库。
Ubuntu 14.04, Debian 7 Wheezy

对于使用 upstart 的系统而言,编辑 /etc/default/docker 文件,在其中的 DOCKER_OPTS 中增加如下内容:

DOCKER_OPTS="--registry-mirror=https://registry.docker-cn.com --insecure-registries=192.168.199.100:5000"

重新启动服务。

$ sudo service docker restart

Ubuntu 16.04+, Debian 8+, centos 7
对于使用 systemd 的系统,请在 /etc/docker/daemon.json 中写入如下内容(如果文件不存在请新建该文件)

{"registry-mirror": ["https://registry.docker-cn.com"],"insecure-registries": ["192.168.199.100:5000"]
}

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

对于 Docker for Windows 、 Docker for Mac 在设置中编辑 daemon.json 增加和上边一样的字符串即可。

私有仓库高级配置

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

新建一个文件夹,以下步骤均在该文件夹中进行。

准备站点证书

如果你拥有一个域名,国内各大云服务商均提供免费的站点证书。你也可以使用 openssl 自行签发证书。

这里假设我们将要搭建的私有仓库地址为 docker.domain.com,下面我们介绍使用 openssl 自行签发 docker.domain.com 的站点 SSL 证书。

第一步创建 CA 私钥。

$ openssl genrsa -out "root-ca.key" 4096

第二步利用私钥创建 CA 根证书请求文件。

$ openssl req \-new -key "root-ca.key" \-out "root-ca.csr" -sha256 \-subj '/C=CN/ST=Shanxi/L=Datong/O=Your Company Name/CN=Your Company Name Docker Registry CA'

以上命令中 -subj 参数里的 /C 表示国家,如 CN;/ST 表示省;/L 表示城市或者地区;/O 表示组织名;/CN 通用名称。

第三步配置 CA 根证书,新建 root-ca.cnf。

[root_ca]
basicConstraints = critical,CA:TRUE,pathlen:1
keyUsage = critical, nonRepudiation, cRLSign, keyCertSign
subjectKeyIdentifier=hash

第四步签发根证书。

$ openssl x509 -req  -days 3650  -in "root-ca.csr" \-signkey "root-ca.key" -sha256 -out "root-ca.crt" \-extfile "root-ca.cnf" -extensions \root_ca

第五步生成站点 SSL 私钥。

$ openssl genrsa -out "docker.domain.com.key" 4096

第六步使用私钥生成证书请求文件。

$ openssl req -new -key "docker.domain.com.key" -out "site.csr" -sha256 \-subj '/C=CN/ST=Shanxi/L=Datong/O=Your Company Name/CN=docker.domain.com'

第七步配置证书,新建 site.cnf 文件。

[server]
authorityKeyIdentifier=keyid,issuer
basicConstraints = critical,CA:FALSE
extendedKeyUsage=serverAuth
keyUsage = critical, digitalSignature, keyEncipherment
subjectAltName = DNS:docker.domain.com, IP:127.0.0.1
subjectKeyIdentifier=hash

第八步签署站点 SSL 证书。

$ openssl x509 -req -days 750 -in "site.csr" -sha256 \-CA "root-ca.crt" -CAkey "root-ca.key"  -CAcreateserial \-out "docker.domain.com.crt" -extfile "site.cnf" -extensions server

这样已经拥有了 docker.domain.com 的网站 SSL 私钥 docker.domain.com.key 和 SSL 证书 docker.domain.com.crt。

新建 ssl 文件夹并将 docker.domain.com.key docker.domain.com.crt 这两个文件移入,删除其他文件。

配置私有仓库

私有仓库默认的配置文件位于 /etc/docker/registry/config.yml,我们先在本地编辑 config.yml,之后挂载到容器中。

version: 0.1
log:accesslog:disabled: truelevel: debugformatter: textfields:service: registryenvironment: staging
storage:delete:enabled: truecache:blobdescriptor: inmemoryfilesystem:rootdirectory: /var/lib/registry
auth:htpasswd:realm: basic-realmpath: /etc/docker/registry/auth/nginx.htpasswd
http:addr: :443host: https://docker.domain.comheaders:X-Content-Type-Options: [nosniff]http2:disabled: falsetls:certificate: /etc/docker/registry/ssl/docker.domain.com.crtkey: /etc/docker/registry/ssl/docker.domain.com.key
health:storagedriver:enabled: trueinterval: 10s
threshold: 3

生成 http 认证文件

$ mkdir auth$ docker run --rm \--entrypoint htpasswd \registry \-Bbn username password > auth/nginx.htpasswd

将上面的 username password 替换为你自己的用户名和密码。

编辑 docker-compose.yml

version: '3'services:registry:image: registryports:- "443:443"volumes:- ./:/etc/docker/registry- registry-data:/var/lib/registryvolumes:registry-data:

修改 hosts

编辑 /etc/hosts

docker.domain.com 127.0.0.1

启动

$ docker-compose up -d

这样我们就搭建好了一个具有权限认证、TLS 的私有仓库,接下来我们测试其功能是否正常。

测试私有仓库功能

登录到私有仓库。

$ docker login docker.domain.com

尝试推送、拉取镜像。

$ docker pull ubuntu:17.10$ docker tag ubuntu:17.10 docker.domain.com/username/ubuntu:17.10$ docker push docker.domain.com/username/ubuntu:17.10$ docker image rm docker.domain.com/username/ubuntu:17.10$ docker pull docker.domain.com/username/ubuntu:17.10

如果我们退出登录,尝试推送镜像。

$ docker logout docker.domain.com$ docker push docker.domain.com/username/ubuntu:17.10no basic auth credentials

发现会提示没有登录,不能将镜像推送到私有仓库中。

注意事项

如果你本机占用了 443 端口,你可以配置 Nginx 代理,这里不再赘述。

Docker 数据管理

这一章介绍如何在 Docker 内部以及容器之间管理数据,在容器中管理数据主要有两种方式:

● 数据卷(Volumes)
● 挂载主机目录 (Bind mounts)

例子,挂载数据卷html到nginx的网站根目录下
docker run --name nginx1 -d -p 80:80 -v html:/usr/share/nginx/html nginx
卷在宿主机存放的目录是:/var/lib/docker/volumes
所以html卷的绝对路径是:/var/lib/docker/volumes/html

数据卷

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

● 数据卷 可以在容器之间共享和重用
● 对 数据卷 的修改会立马生效
● 对 数据卷 的更新,不会影响镜像
● 数据卷 默认会一直存在,即使容器被删除

注意:数据卷 的使用,类似于 Linux 下对目录或文件进行 mount,镜像中的被指定为挂载点的目录中的文件会隐藏掉,能显示看的是挂载的 数据卷。

选择 -v 还是 -–mount 参数

Docker 新用户应该选择 --mount 参数,经验丰富的 Docker 使用者对 -v 或者 --volume 已经很熟悉了,但是推荐使用 --mount 参数。

创建一个数据卷

$ docker volume create my-vol

查看所有的 数据卷

$ docker volume lslocal               my-vol

在主机里使用以下命令可以查看指定 数据卷 的信息

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

启动一个挂载数据卷的容器

在用 docker run 命令的时候,使用 --mount 标记来将 数据卷 挂载到容器里。在一次 docker run 中可以挂载多个 数据卷。

下面创建一个名为 web 的容器,并加载一个 数据卷 到容器的 /webapp 目录。

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

查看数据卷的具体信息

在主机里使用以下命令可以查看 web 容器的信息

$ docker inspect web

数据卷 信息在 “Mounts” Key 下面

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

删除数据卷

$ docker volume rm my-vol

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

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

$ docker volume prune

挂载主机目录

选择 -v 还是 -–mount 参数

Docker 新用户应该选择 --mount 参数,经验丰富的 Docker 使用者对 -v 或者 --volume 已经很熟悉了,但是推荐使用 --mount 参数。

挂载一个主机目录作为数据卷

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

$ 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

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

加了 readonly 之后,就挂载为 只读 了。如果你在容器内 /opt/webapp 目录新建文件,会显示如下错误

/opt/webapp # touch new.txt
touch: new.txt: Read-only file system

查看数据卷的具体信息

在主机里使用以下命令可以查看 web 容器的信息

$ docker inspect web

挂载主机目录 的配置信息在 “Mounts” Key 下面

"Mounts": [{"Type": "bind","Source": "/src/webapp","Destination": "/opt/webapp","Mode": "","RW": true,"Propagation": "rprivate"}
],

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

–mount 标记也可以从主机挂载单个文件到容器中

$ docker run --rm -it \# -v $HOME/.bash_history:/root/.bash_history \--mount type=bind,source=$HOME/.bash_history,target=/root/.bash_history \ubuntu:17.10 \bashroot@2affd44b4667:/# history
1  ls
2  diskutil list

这样就可以记录在容器输入过的命令了。

Docker Compose 项目

Docker Compose 是 Docker 官方编排(Orchestration)项目之一,负责快速的部署分布式应用。

本章将介绍 Compose 项目情况以及安装和使用。

Compose 简介

Compose 项目是 Docker 官方的开源项目,负责实现对 Docker 容器集群的快速编排。从功能上看,跟 OpenStack 中的 Heat 十分类似。

其代码目前在 https://github.com/docker/compose 上开源。

Compose 定位是 「定义和运行多个 Docker 容器的应用(Defining and running multi-container Docker applications)」,其前身是开源项目 Fig。

通过第一部分中的介绍,我们知道使用一个 Dockerfile 模板文件,可以让用户很方便的定义一个单独的应用容器。然而,在日常工作中,经常会碰到需要多个容器相互配合来完成某项任务的情况。例如要实现一个 Web 项目,除了 Web 服务容器本身,往往还需要再加上后端的数据库服务容器,甚至还包括负载均衡容器等。

Compose 恰好满足了这样的需求。它允许用户通过一个单独的 docker-compose.yml 模板文件(YAML 格式)来定义一组相关联的应用容器为一个项目(project)。

Compose 中有两个重要的概念:

● 服务 (service):一个应用的容器,实际上可以包括若干运行相同镜像的容器实例。
● 项目 (project):由一组关联的应用容器组成的一个完整业务单元,在 docker-compose.yml 文件中定义。

Compose 的默认管理对象是项目,通过子命令对项目中的一组容器进行便捷地生命周期管理。

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

安装与卸载

Compose 支持 Linux、macOS、Windows 10 三大平台。

Compose 可以通过 Python 的包管理工具 pip 进行安装,也可以直接下载编译好的二进制文件使用,甚至能够直接在 Docker 容器中运行。

前两种方式是传统方式,适合本地环境下安装使用;最后一种方式则不破坏系统环境,更适合云计算场景。
这里我们只介绍二进制安装方式,PIP安装的方式比较麻烦。

Docker for Mac 、Docker for Windows 自带 docker-compose 二进制文件,安装 Docker 之后可以直接使用。

lqf@ubuntu:~/0voice/docker$ docker-compose  -version
docker-compose version 1.27.4, build 40524192

Linux 系统请使用以下介绍的方法安装。

二进制包

在 Linux 上的也安装十分简单,从 官方 GitHub Release 处直接下载编译好的二进制文件即可。

例如,在 Linux 64 位系统上直接下载对应的二进制包。

# 先把docker-compose文件dump到当前目录
$ wget  https://github.com/docker/compose/releases/download/1.27.4/docker-compose-Linux-x86_64
# 然后拷贝到/usr/bin/
$ sudo cp -arf docker-compose-Linux-x86_64  /usr/bin/docker-compose
$ sudo chmod +x /usr/bin/docker-compose

卸载

如果是二进制包方式安装的,删除二进制文件即可。

$ sudo rm /usr/bin/docker-compose

实例-Web计数

术语

首先介绍几个术语。

● 服务 (service):一个应用容器,实际上可以运行多个相同镜像的实例。
● 项目 (project):由一组关联的应用容器组成的一个完整业务单元。

可见,一个项目可以由多个服务(容器)关联而成,Compose 面向项目进行管理。

准备

创建一个测试目录:

$ mkdir composetest
$ cd composetest

在测试目录中创建一个名为 app.py 的文件,并复制粘贴以下内容:
composetest/app.py 文件代码

import time
import redis
from flask import Flask
app = Flask(__name__)
cache = redis.Redis(host='redis', port=6379)
def get_hit_count():retries = 5while True:try:return cache.incr('hits')except redis.exceptions.ConnectionError as exc:if retries == 0:raise excretries -= 1time.sleep(0.5)
@app.route('/')
def hello():count = get_hit_count()return 'Hello World! I have been seen {} times.\n'.format(count)

在此示例中,redis 是应用程序网络上的 redis 容器的主机名,该主机使用的端口为 6379。
在 composetest 目录中创建另一个名为 requirements.txt 的文件,内容如下:

flask
redis

创建 Dockerfile 文件

在 composetest 目录中,创建一个名为的文件 Dockerfile,内容如下:

FROM python:3.7-alpine
WORKDIR /code
ENV FLASK_APP app.py
ENV FLASK_RUN_HOST 0.0.0.0
# RUN apk add --no-cache gcc musl-dev linux-headers
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
COPY . .
CMD ["flask", "run"]

Dockerfile 内容解释:
● FROM python:3.7-alpine: 从 Python 3.7 映像开始构建镜像。
● WORKDIR /code: 将工作目录设置为 /code。
ENV FLASK_APP app.py
ENV FLASK_RUN_HOST 0.0.0.0
● 设置 flask 命令使用的环境变量。
● RUN apk add --no-cache gcc musl-dev linux-headers: 安装 gcc,以便诸如 MarkupSafe 和 SQLAlchemy 之类的 Python 包可以编译加速。这里先注释掉,下载太费时间了。
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
● 复制 requirements.txt 并安装 Python 依赖项。
● COPY . .: 将 . 项目中的当前目录复制到 . 镜像中的工作目录。
● CMD [“flask”, “run”]: 容器提供默认的执行命令为:flask run。

创建 docker-compose.yml

在测试目录中创建一个名为 docker-compose.yml 的文件,然后粘贴以下内容:

# yaml 配置
version: '3'
services:web:build: .ports:- "5000:5000"redis:image: "redis:alpine"

该 Compose 文件定义了两个服务:web 和 redis。
● web:该 web 服务使用从 Dockerfile 当前目录中构建的镜像。然后,它将容器和主机绑定到暴露的端口 5000。此示例服务使用 Flask Web 服务器的默认端口 5000 。
● redis:该 redis 服务使用 Docker Hub 的公共 Redis 映像。

使用 Compose 命令构建和运行您的应用

在测试目录中,执行以下命令来启动应用程序:

docker-compose up

如果你想在后台执行该服务可以加上 -d 参数:docker-compose up -d

运行后的效果

在这里插入图片描述

网页访问
在这里插入图片描述

查看目前运行的镜像
docker container ls -a

lqf@ubuntu:~/0voice/docker$ docker container ls -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                      PORTS                    NAMES
78953a0d581d        composetest_web     "flask run"              2 minutes ago       Up 2 minutes                0.0.0.0:5000->5000/tcp   composetest_web_1
62e58d71b9fd        redis:alpine        "docker-entrypoint.s…"   2 minutes ago       Up 2 minutes                6379/tcp                 composetest_redis_1

Compose 命令说明

命令对象与格式

对于 Compose 来说,大部分命令的对象既可以是项目本身,也可以指定为项目中的服务或者容器。如果没有特别的说明,命令对象将是项目,这意味着项目中所有的服务都会受到命令影响。

执行 docker-compose [COMMAND] --help 或者 docker-compose help [COMMAND] 可以查看具体某个命令的使用格式。

docker-compose 命令的基本的使用格式是

docker-compose [-f=…] [options] [COMMAND] [ARGS…]

命令选项

● -f, --file FILE 指定使用的 Compose 模板文件,默认为 docker-compose.yml,可以多次指定。
● -p, --project-name NAME 指定项目名称,默认将使用所在目录名称作为项目名。
● --x-networking 使用 Docker 的可拔插网络后端特性
● --x-network-driver DRIVER 指定网络后端的驱动,默认为 bridge
● --verbose 输出更多调试信息。
● -v, --version 打印版本并退出。

命令使用说明

build
格式为 docker-compose build [options] [SERVICE…]。

构建(重新构建)项目中的服务容器。

服务容器一旦构建后,将会带上一个标记名,例如对于 web 项目中的一个 db 容器,可能是 web_db。

可以随时在项目目录下运行 docker-compose build 来重新构建服务。

选项包括:

● --force-rm 删除构建过程中的临时容器。
● --no-cache 构建镜像过程中不使用 cache(这将加长构建过程)。
● --pull 始终尝试通过 pull 来获取更新版本的镜像。

config
验证 Compose 文件格式是否正确,若正确则显示配置,若格式错误显示错误原因。

down
此命令将会停止 up 命令所启动的容器,并移除网络

exec
进入指定的容器。

help
获得一个命令的帮助。

images
列出 Compose 文件中包含的镜像。

kill
格式为 docker-compose kill [options] [SERVICE…]。

通过发送 SIGKILL 信号来强制停止服务容器。

支持通过 -s 参数来指定发送的信号,例如通过如下指令发送 SIGINT 信号。
$ docker-compose kill -s SIGINT

logs
格式为 docker-compose logs [options] [SERVICE…]。

查看服务容器的输出。默认情况下,docker-compose 将对不同的服务输出使用不同的颜色来区分。可以通过 --no-color 来关闭颜色。

该命令在调试问题的时候十分有用。

pause
格式为 docker-compose pause [SERVICE…]。

暂停一个服务容器。

port
格式为 docker-compose port [options] SERVICE PRIVATE_PORT。

打印某个容器端口所映射的公共端口。

选项:
● --protocol=proto 指定端口协议,tcp(默认值)或者 udp。
● --index=index 如果同一服务存在多个容器,指定命令对象容器的序号(默认为 1)。

ps
格式为 docker-compose ps [options] [SERVICE…]。

列出项目中目前的所有容器。

选项:
● -q 只打印容器的 ID 信息。

pull
格式为 docker-compose pull [options] [SERVICE…]。

拉取服务依赖的镜像。

选项:
● --ignore-pull-failures 忽略拉取镜像过程中的错误。

push
推送服务依赖的镜像到 Docker 镜像仓库。

restart
格式为 docker-compose restart [options] [SERVICE…]。

重启项目中的服务。

选项:
● -t, --timeout TIMEOUT 指定重启前停止容器的超时(默认为 10 秒)。

rm
格式为 docker-compose rm [options] [SERVICE…]。

删除所有(停止状态的)服务容器。推荐先执行 docker-compose stop 命令来停止容器。

选项:
● -f, --force 强制直接删除,包括非停止状态的容器。一般尽量不要使用该选项。
● -v 删除容器所挂载的数据卷。

run

格式为 docker-compose run [options] [-p PORT…] [-e KEY=VAL…] SERVICE [COMMAND] [ARGS…]。

在指定服务上执行一个命令。

例如:
$ docker-compose run ubuntu ping docker.com

将会启动一个 ubuntu 服务容器,并执行 ping docker.com 命令。

默认情况下,如果存在关联,则所有关联的服务将会自动被启动,除非这些服务已经在运行中。

该命令类似启动容器后运行指定的命令,相关卷、链接等等都将会按照配置自动创建。

两个不同点:
● 给定命令将会覆盖原有的自动运行命令;
● 不会自动创建端口,以避免冲突。

如果不希望自动启动关联的容器,可以使用 --no-deps 选项,例如
$ docker-compose run --no-deps web python manage.py shell

将不会启动 web 容器所关联的其它容器。

选项:
● -d 后台运行容器。
● --name NAME 为容器指定一个名字。
● --entrypoint CMD 覆盖默认的容器启动指令。
● -e KEY=VAL 设置环境变量值,可多次使用选项来设置多个环境变量。
● -u, --user="" 指定运行容器的用户名或者 uid。
● --no-deps 不自动启动关联的服务容器。
● --rm 运行命令后自动删除容器,d 模式下将忽略。
● -p, --publish=[] 映射容器端口到本地主机。
● --service-ports 配置服务端口并映射到本地主机。
● -T 不分配伪 tty,意味着依赖 tty 的指令将无法运行。

scale
格式为 docker-compose scale [options] [SERVICE=NUM…]。

设置指定服务运行的容器个数。

通过 service=num 的参数来设置数量。例如:

$ docker-compose scale web=3 db=2

将启动 3 个容器运行 web 服务,2 个容器运行 db 服务。

一般的,当指定数目多于该服务当前实际运行容器,将新创建并启动容器;反之,将停止容器。

选项:
● -t, --timeout TIMEOUT 停止容器时候的超时(默认为 10 秒)。

start

格式为 docker-compose start [SERVICE…]。

启动已经存在的服务容器。

stop
格式为 docker-compose stop [options] [SERVICE…]。

停止已经处于运行状态的容器,但不删除它。通过 docker-compose start 可以再次启动这些容器。

选项:
● -t, --timeout TIMEOUT 停止容器时候的超时(默认为 10 秒)。

top
查看各个服务容器内运行的进程。

unpause
格式为 docker-compose unpause [SERVICE…]。

恢复处于暂停状态中的服务。

up
格式为 docker-compose up [options] [SERVICE…]。

该命令十分强大,它将尝试自动完成包括构建镜像,(重新)创建服务,启动服务,并关联服务相关容器的一系列操作。

链接的服务都将会被自动启动,除非已经处于运行状态。

可以说,大部分时候都可以直接通过该命令来启动一个项目。

默认情况,docker-compose up 启动的容器都在前台,控制台将会同时打印所有容器的输出信息,可以很方便进行调试。

当通过 Ctrl-C 停止命令时,所有容器将会停止。

如果使用 docker-compose up -d,将会在后台启动并运行所有的容器。一般推荐生产环境下使用该选项。

默认情况,如果服务容器已经存在,docker-compose up 将会尝试停止容器,然后重新创建(保持使用 volumes-from 挂载的卷),以保证新启动的服务匹配 docker-compose.yml 文件的最新内容。如果用户不希望容器被停止并重新创建,可以使用 docker-compose up --no-recreate。这样将只会启动处于停止状态的容器,而忽略已经运行的服务。如果用户只想重新部署某个服务,可以使用 docker-compose up --no-deps -d <SERVICE_NAME> 来重新创建服务并后台停止旧服务,启动新服务,并不会影响到其所依赖的服务。

选项:
● -d 在后台运行服务容器。
● --no-color 不使用颜色来区分不同的服务的控制台输出。
● --no-deps 不启动服务所链接的容器。
● --force-recreate 强制重新创建容器,不能与 --no-recreate 同时使用。
● --no-recreate 如果容器已经存在了,则不重新创建,不能与 --force-recreate 同时使用。
● --no-build 不自动构建缺失的服务镜像。
● -t, --timeout TIMEOUT 停止容器时候的超时(默认为 10 秒)。

version
格式为 docker-compose version。

打印版本信息。

Compose 模板文件

模板文件是使用 Compose 的核心,涉及到的指令关键字也比较多。但大家不用担心,这里面大部分指令跟 docker run 相关参数的含义都是类似的。

默认的模板文件名称为 docker-compose.yml,格式为 YAML 格式。
version: “3”

services:
webapp:
image: examples/web
ports:
- “80:80”
volumes:
- “/data”

注意每个服务都必须通过 image 指令指定镜像或 build 指令(需要 Dockerfile)等来自动构建生成镜像。

如果使用 build 指令,在 Dockerfile 中设置的选项(例如:CMD, EXPOSE, VOLUME, ENV 等) 将会自动被获取,无需在 docker-compose.yml 中再次设置。

下面分别介绍各个指令的用法。

build
指定 Dockerfile 所在文件夹的路径(可以是绝对路径,或者相对 docker-compose.yml 文件的路径)。 Compose 将会利用它自动构建这个镜像,然后使用这个镜像。

version: ‘3’
services:

webapp:
build: ./dir

你也可以使用 context 指令指定 Dockerfile 所在文件夹的路径。

使用 dockerfile 指令指定 Dockerfile 文件名。

使用 arg 指令指定构建镜像时的变量。
version: ‘3’
services:

webapp:
build:
context: ./dir
dockerfile: Dockerfile-alternate
args:
buildno: 1

使用 cache_from 指定构建镜像的缓存
build:
context: .
cache_from:
- alpine:latest
- corp/web_app:3.14

cap_add, cap_drop
指定容器的内核能力(capacity)分配。

例如,让容器拥有所有能力可以指定为:
cap_add:

  • ALL

去掉 NET_ADMIN 能力可以指定为:
cap_drop:

  • NET_ADMIN

command
覆盖容器启动后默认执行的命令。
command: echo “hello world”

configs
仅用于 Swarm mode,详细内容请查看 Swarm mode 一节。

cgroup_parent
指定父 cgroup 组,意味着将继承该组的资源限制。

例如,创建了一个 cgroup 组名称为 cgroups_1。
cgroup_parent: cgroups_1

container_name
指定容器名称。默认将会使用 项目名称_服务名称_序号 这样的格式。

container_name: docker-web-container

注意: 指定容器名称后,该服务将无法进行扩展(scale),因为 Docker 不允许多个容器具有相同的名称。

deploy
仅用于 Swarm mode,详细内容请查看 Swarm mode 一节

devices
指定设备映射关系。
devices:

  • “/dev/ttyUSB1:/dev/ttyUSB0”

depends_on
解决容器的依赖、启动先后的问题。以下例子中会先启动 redis db 再启动 web
version: ‘3’

services:
web:
build: .
depends_on:
- db
- redis

redis:
image: redis

db:
image: postgres

注意:web 服务不会等待 redis db 「完全启动」之后才启动。

dns
自定义 DNS 服务器。可以是一个值,也可以是一个列表。
dns: 8.8.8.8

dns:

  • 8.8.8.8
  • 114.114.114.114

dns_search
配置 DNS 搜索域。可以是一个值,也可以是一个列表。
dns_search: example.com

dns_search:

  • domain1.example.com
  • domain2.example.com

tmpfs
挂载一个 tmpfs 文件系统到容器。

tmpfs: /run
tmpfs:

  • /run
  • /tmp

env_file
从文件中获取环境变量,可以为单独的文件路径或列表。

如果通过 docker-compose -f FILE 方式来指定 Compose 模板文件,则 env_file 中变量的路径会基于模板文件路径。

如果有变量名称与 environment 指令冲突,则按照惯例,以后者为准。
env_file: .env

env_file:

  • ./common.env
  • ./apps/web.env
  • /opt/secrets.env

环境变量文件中每一行必须符合格式,支持 # 开头的注释行。

# common.env: Set development environment
PROG_ENV=developmentenvironment

设置环境变量。你可以使用数组或字典两种格式。

只给定名称的变量会自动获取运行 Compose 主机上对应变量的值,可以用来防止泄露不必要的数据。
environment:
RACK_ENV: development
SESSION_SECRET:

environment:

  • RACK_ENV=development
  • SESSION_SECRET

如果变量名称或者值中用到 true|false,yes|no 等表达 布尔 含义的词汇,最好放到引号里,避免 YAML 自动解析某些内容为对应的布尔语义。这些特定词汇,包括
y|Y|yes|Yes|YES|n|N|no|No|NO|true|True|TRUE|false|False|FALSE|on|On|ON|off|Off|OFF

expose
暴露端口,但不映射到宿主机,只被连接的服务访问。

仅可以指定内部端口为参数
expose:

  • “3000”
  • “8000”

external_links
注意:不建议使用该指令。

链接到 docker-compose.yml 外部的容器,甚至并非 Compose 管理的外部容器。
external_links:

  • redis_1
  • project_db_1:mysql
  • project_db_1:postgresql

extra_hosts
类似 Docker 中的 --add-host 参数,指定额外的 host 名称映射信息。
extra_hosts:

  • “googledns:8.8.8.8”
  • “dockerhub:52.1.157.61”

会在启动后的服务容器中 /etc/hosts 文件中添加如下两条条目。
8.8.8.8 googledns
52.1.157.61 dockerhub

healthcheck
通过命令检查容器是否健康运行。
healthcheck:
test: [“CMD”, “curl”, “-f”, “http://localhost”]
interval: 1m30s
timeout: 10s
retries: 3

image
指定为镜像名称或镜像 ID。如果镜像在本地不存在,Compose 将会尝试拉取这个镜像。
image: ubuntu
image: orchardup/postgresql
image: a4bc65fd

labels
为容器添加 Docker 元数据(metadata)信息。例如可以为容器添加辅助说明信息。
labels:
com.startupteam.description: “webapp for a startup team”
com.startupteam.department: “devops department”
com.startupteam.release: “rc3 for v1.0”

links
注意:不推荐使用该指令。

logging
配置日志选项。
logging:
driver: syslog
options:
syslog-address: “tcp://192.168.0.42:123”

目前支持三种日志驱动类型。
driver: “json-file”
driver: “syslog”
driver: “none”

options 配置日志驱动的相关参数。
options:
max-size: “200k”
max-file: “10”

network_mode
设置网络模式。使用和 docker run 的 --network 参数一样的值。
network_mode: “bridge”
network_mode: “host”
network_mode: “none”
network_mode: “service:[service name]”
network_mode: “container:[container name/id]”

networks
配置容器连接的网络。
version: “3”
services:

some-service:
networks:
- some-network
- other-network

networks:
some-network:
other-network:

pid
跟主机系统共享进程命名空间。打开该选项的容器之间,以及容器和宿主机系统之间可以通过进程 ID 来相互访问和操作。
pid: “host”

ports
暴露端口信息。

使用宿主端口:容器端口 (HOST:CONTAINER) 格式,或者仅仅指定容器的端口(宿主将会随机选择端口)都可以。

ports:

  • “3000”
  • “8000:8000”
  • “49100:22”
  • “127.0.0.1:8001:8001”

注意:当使用 HOST:CONTAINER 格式来映射端口时,如果你使用的容器端口小于 60 并且没放到引号里,可能会得到错误结果,因为 YAML 会自动解析 xx:yy 这种数字格式为 60 进制。为避免出现这种问题,建议数字串都采用引号包括起来的字符串格式。

secrets
存储敏感数据,例如 mysql 服务密码。

version: “3.1”
services:

mysql:
image: mysql
environment:
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password
secrets:
- db_root_password
- my_other_secret

secrets:
my_secret:
file: ./my_secret.txt
my_other_secret:
external: true

security_opt
指定容器模板标签(label)机制的默认属性(用户、角色、类型、级别等)。例如配置标签的用户名和角色名。

security_opt:
- label:user:USER
- label:role:ROLE

stop_signal
设置另一个信号来停止容器。在默认情况下使用的是 SIGTERM 停止容器。

stop_signal: SIGUSR1

sysctls
配置容器内核参数。

sysctls:
net.core.somaxconn: 1024
net.ipv4.tcp_syncookies: 0

sysctls:

  • net.core.somaxconn=1024
  • net.ipv4.tcp_syncookies=0

ulimits
指定容器的 ulimits 限制值。

例如,指定最大进程数为 65535,指定文件句柄数为 20000(软限制,应用可以随时修改,不能超过硬限制) 和 40000(系统硬限制,只能 root 用户提高)。

ulimits:
nproc: 65535
nofile:
soft: 20000
hard: 40000

volumes
数据卷所挂载路径设置。可以设置宿主机路径 (HOST:CONTAINER) 或加上访问模式 (HOST:CONTAINER:ro)。

该指令中路径支持相对路径。
volumes:

  • /var/lib/mysql
  • cache/:/tmp/cache
  • ~/configs:/etc/configs/:ro

其它指令
此外,还有包括 domainname, entrypoint, hostname, ipc, mac_address, privileged, read_only, shm_size, restart, stdin_open, tty, user, working_dir 等指令,基本跟 docker run 中对应参数的功能一致。

指定服务容器启动后执行的入口文件。
entrypoint: /code/entrypoint.sh

指定容器中运行应用的用户名。
user: nginx

指定容器中工作目录。
working_dir: /code

指定容器中搜索域名、主机名、mac 地址等。
domainname: your_website.com
hostname: test
mac_address: 08-00-27-00-0C-0A

允许容器中运行一些特权命令。
privileged: true

指定容器退出后的重启策略为始终重启。该命令对保持服务始终运行十分有效,在生产环境中推荐配置为 always 或者 unless-stopped。
restart: always

以只读模式挂载容器的 root 文件系统,意味着不能对容器内容进行修改。
read_only: true

打开标准输入,可以接受外部输入。
stdin_open: true

模拟一个伪终端。
tty: true

读取变量

Compose 模板文件支持动态读取主机的系统环境变量和当前目录下的 .env 文件中的变量。

例如,下面的 Compose 文件将从运行它的环境中读取变量 ${MONGO_VERSION} 的值,并写入执行的指令中。
version: “3”
services:

db:
image: “mongo:${MONGO_VERSION}”

如果执行 MONGO_VERSION=3.2 docker-compose up 则会启动一个 mongo:3.2 镜像的容器;如果执行 MONGO_VERSION=2.8 docker-compose up 则会启动一个 mongo:2.8 镜像的容器。

若当前目录存在 .env 文件,执行 docker-compose 命令时将从该文件中读取变量。

在当前目录新建 .env 文件并写入以下内容。

# 支持 # 号注释
MONGO_VERSION=3.6

执行 docker-compose up 则会启动一个 mongo:3.6 镜像的容器。

实战:docker-compose部署redis-cluster

按照 Redis 官网:https://redis.io/topics/cluster-tutorial 的提示,为了使 Docker 与 Redis Cluster 兼容,您需要使用 Docker 的 host 网络模式。
host 网络模式需要在创建容器时通过参数 --net host 或者 --network host 指定,host 网络模式可以让容器共享宿主机网络栈,容器将不会虚拟出自己的网卡,配置自己的 IP 等,而是使用宿主机的 IP 和端口。
关于 Docker 网络模式更多的内容请阅读《Docker 网络模式详解及容器间网络通信》。

环境

为了让环境更加容易搭建,本文使用单机环境:
● 192.168.2.132

搭建

整体搭建步骤主要分为以下几步:
● 下载 Redis 镜像(其实这步可以省略,因为创建容器时,如果本地镜像不存在,就会去远程拉取);
● 编写 Redis 配置文件;
● 编写 Docker Compose 模板文件;
● 创建并启动所有服务容器;
● 创建 Redis Cluster 集群。
编写 Redis 配置文件
创建目录及文件
在 192.168.2.132

# 创建目录
lqf@ubuntu:~/0voice/docker$ mkdir -p  redis-cluster
# 切换至指定目录
lqf@ubuntu:~/0voice/docker$ cd ~/0voice/docker/redis-cluster
# 编写 redis-cluster.tmpl 文件
vim redis-cluster.tmpl

编写配置文件
192.168.2.132 机器的 redis-cluster.tmpl 文件内容如下:

port ${PORT}
requirepass 1234
masterauth 1234
protected-mode no
daemonize no
appendonly yes
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 15000
cluster-announce-ip 192.168.2.132
cluster-announce-port ${PORT}
cluster-announce-bus-port 1${PORT}

port:节点端口;
● requirepass:添加访问认证;
● masterauth:如果主节点开启了访问认证,从节点访问主节点需要认证;
● protected-mode:保护模式,默认值 yes,即开启。开启保护模式以后,需配置 bind ip 或者设置访问密码;关闭保护模式,外部网络可以直接访问;
● daemonize:是否以守护线程的方式启动(后台启动),默认 no;
● appendonly:是否开启 AOF 持久化模式,默认 no;
● cluster-enabled:是否开启集群模式,默认 no;
● cluster-config-file:集群节点信息文件;
● cluster-node-timeout:集群节点连接超时时间;
● cluster-announce-ip:集群节点 IP,填写宿主机的 IP;
● cluster-announce-port:集群节点映射端口;
● cluster-announce-bus-port:集群节点总线端口。
每个 Redis 集群节点都需要打开两个 TCP 连接。一个用于为客户端提供服务的正常 Redis TCP 端口,例如 6379。还有一个基于 6379 端口加 10000 的端口,比如 16379。
第二个端口用于集群总线,这是一个使用二进制协议的节点到节点通信通道。节点使用集群总线进行故障检测、配置更新、故障转移授权等等。客户端永远不要尝试与集群总线端口通信,与正常的 Redis 命令端口通信即可,但是请确保防火墙中的这两个端口都已经打开,否则 Redis 集群节点将无法通信。
在 redis-cluster 目录下执行以下命令:

for port in `seq 6371 6376`; do \mkdir -p ${port}/conf \&& PORT=${port} envsubst < redis-cluster.tmpl > ${port}/conf/redis.conf \&& mkdir -p ${port}/data;\
done

执行查看命令结果如下,如果没有 tree 命令先安装 sudo apt install tree。

以下内容为每个节点的配置文件详细信息(查看)。

lqf@ubuntu:~/0voice/docker/redis-cluster$ cat /usr/local/docker-redis/redis-cluster/637{1..6}/conf/redis.conf
port 6371
requirepass 1234
masterauth 1234
protected-mode no
daemonize no
appendonly yes
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 15000
cluster-announce-ip 192.168.2.132
cluster-announce-port 6371
cluster-announce-bus-port 16371
port 6372
requirepass 1234
masterauth 1234
protected-mode no
daemonize no
appendonly yes
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 15000
cluster-announce-ip 192.168.2.132
cluster-announce-port 6372
cluster-announce-bus-port 16372
port 6373
requirepass 1234
masterauth 1234
protected-mode no
daemonize no
appendonly yes
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 15000
cluster-announce-ip 192.168.2.132
cluster-announce-port 6373
cluster-announce-bus-port 16373
port 6374
requirepass 1234
masterauth 1234
protected-mode no
daemonize no
appendonly yes
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 15000
cluster-announce-ip 192.168.2.132
cluster-announce-port 6374
cluster-announce-bus-port 16374
port 6375
requirepass 1234
masterauth 1234
protected-mode no
daemonize no
appendonly yes
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 15000
cluster-announce-ip 192.168.2.132
cluster-announce-port 6375
cluster-announce-bus-port 16375
port 6376
requirepass 1234
masterauth 1234
protected-mode no
daemonize no
appendonly yes
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 15000
cluster-announce-ip 192.168.2.132
cluster-announce-port 6376
cluster-announce-bus-port 16376

编写 Docker Compose 模板文件
在 docker-redis 目录下创建 docker-compose.yml 文件并编辑。

# 描述 Compose 文件的版本信息
version: "3.8"
# 定义服务,可以多个
services:redis-6371: # 服务名称image: redis # 创建容器时所需的镜像container_name: redis-6371 # 容器名称restart: always # 容器总是重新启动network_mode: "host" # host 网络模式volumes: # 数据卷,目录挂载- /home/lqf/0voice/docker/redis-cluster/6371/conf/redis.conf:/usr/local/etc/redis/redis.conf- /home/lqf/0voice/docker/redis-cluster/6371/data:/datacommand: redis-server /usr/local/etc/redis/redis.conf # 覆盖容器启动后默认执行的命令redis-6372:image: rediscontainer_name: redis-6372network_mode: "host"volumes:- /home/lqf/0voice/docker/redis-cluster/6372/conf/redis.conf:/usr/local/etc/redis/redis.conf- /home/lqf/0voice/docker/redis-cluster/6372/data:/datacommand: redis-server /usr/local/etc/redis/redis.confredis-6373:image: rediscontainer_name: redis-6373network_mode: "host"volumes:- /home/lqf/0voice/docker/redis-cluster/6373/conf/redis.conf:/usr/local/etc/redis/redis.conf- /home/lqf/0voice/docker/redis-cluster/6373/data:/datacommand: redis-server /usr/local/etc/redis/redis.confredis-6374:image: rediscontainer_name: redis-6374network_mode: "host"volumes:- /home/lqf/0voice/docker/redis-cluster/6374/conf/redis.conf:/usr/local/etc/redis/redis.conf- /home/lqf/0voice/docker/redis-cluster/6374/data:/datacommand: redis-server /usr/local/etc/redis/redis.confredis-6375:image: rediscontainer_name: redis-6375network_mode: "host"volumes:- /home/lqf/0voice/docker/redis-cluster/6375/conf/redis.conf:/usr/local/etc/redis/redis.conf- /home/lqf/0voice/docker/redis-cluster/6375/data:/datacommand: redis-server /usr/local/etc/redis/redis.confredis-6376:image: rediscontainer_name: redis-6376network_mode: "host"volumes:- /home/lqf/0voice/docker/redis-cluster/6376/conf/redis.conf:/usr/local/etc/redis/redis.conf- /home/lqf/0voice/docker/redis-cluster/6376/data:/datacommand: redis-server /usr/local/etc/redis/redis.conf

创建并启动所有服务容器
分别在docker-redis 目录下执行以下命令:

docker-compose up 

创建 Redis Cluster 集群
请先确保你的两台机器可以互相通信,然后随便进入一个容器节点,并进入 /usr/local/bin/ 目录:

# 进入容器
docker exec -it redis-6371 bash
# 切换至指定目录
cd /usr/local/bin/

接下来我们就可以通过以下命令实现 Redis Cluster 集群的创建。

redis-cli -a 1234 --cluster create 192.168.2.132:6371 192.168.2.132:6372 192.168.2.132:6373 192.168.2.132:6374 192.168.2.132:6375 192.168.2.132:6376 --cluster-replicas 1

出现选择提示信息,输入 yes,结果如下所示:

集群创建成功如下:

lqf@ubuntu:~/0voice/docker$ redis-cli -a 1234 --cluster create 192.168.2.132:6371 192.168.2.132:6372 192.168.2.132:6373 192.168.2.132:6374 192.168.2.132:6375 192.168.2.132:6376 --cluster-replicas 1
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 192.168.2.132:6374 to 192.168.2.132:6371
Adding replica 192.168.2.132:6375 to 192.168.2.132:6372
Adding replica 192.168.2.132:6376 to 192.168.2.132:6373
>>> Trying to optimize slaves allocation for anti-affinity
[WARNING] Some slaves are in the same host as their master
M: 24b88fac47b96e6a35498e88da64b1ab2f7c4ad3 192.168.2.132:6371slots:[0-5460] (5461 slots) master
M: 7e8a3d1c2e794295a564f1ad7bce6280e1dae2f1 192.168.2.132:6372slots:[5461-10922] (5462 slots) master
M: 9075c25fabecbaba260c27da8573c19d7e4efb46 192.168.2.132:6373slots:[10923-16383] (5461 slots) master
S: 26bb57752a879ad75447c70454bb2ec504183ec3 192.168.2.132:6374replicates 7e8a3d1c2e794295a564f1ad7bce6280e1dae2f1
S: 9417f09604a895191fa1649d712fcd3948b3da6e 192.168.2.132:6375replicates 9075c25fabecbaba260c27da8573c19d7e4efb46
S: 7cd243845357a415ac7923ba9a3ec8c519291bc4 192.168.2.132:6376replicates 24b88fac47b96e6a35498e88da64b1ab2f7c4ad3
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
..
>>> Performing Cluster Check (using node 192.168.2.132:6371)
M: 24b88fac47b96e6a35498e88da64b1ab2f7c4ad3 192.168.2.132:6371slots:[0-5460] (5461 slots) master1 additional replica(s)
S: 7cd243845357a415ac7923ba9a3ec8c519291bc4 192.168.2.132:6376slots: (0 slots) slavereplicates 24b88fac47b96e6a35498e88da64b1ab2f7c4ad3
M: 9075c25fabecbaba260c27da8573c19d7e4efb46 192.168.2.132:6373slots:[10923-16383] (5461 slots) master1 additional replica(s)
S: 26bb57752a879ad75447c70454bb2ec504183ec3 192.168.2.132:6374slots: (0 slots) slavereplicates 7e8a3d1c2e794295a564f1ad7bce6280e1dae2f1
S: 9417f09604a895191fa1649d712fcd3948b3da6e 192.168.2.132:6375slots: (0 slots) slavereplicates 9075c25fabecbaba260c27da8573c19d7e4efb46
M: 7e8a3d1c2e794295a564f1ad7bce6280e1dae2f1 192.168.2.132:6372slots:[5461-10922] (5462 slots) master1 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

至此一个高可用的 Redis Cluster 集群搭建完成,如下图所示,该集群中包含 6 个 Redis 节点,3 主 3 从。三个主节点会分配槽,处理客户端的命令请求,而从节点可用在主节点故障后,顶替主节点。

查看集群状态

我们先进入容器,然后通过一些集群常用的命令查看一下集群的状态。

# 进入容器
docker exec -it redis-6371 bash
# 切换至指定目录
cd /usr/local/bin/

检查集群状态

redis-cli -a 1234 --cluster check 192.168.2.132:6375

查看集群信息和节点信息

# 连接至集群某个节点
redis-cli -c -a 1234 -h 192.168.2.132 -p 6376
# 查看集群信息
CLUSTER INFO
# 查看集群结点信息
cluster nodes

SET/GET

192.168.2.132:6376> set darren king
-> Redirected to slot [2806] located at 192.168.2.132:6371
OK
192.168.2.132:6371> get darren
“king”
192.168.2.132:6371>

多机器部署参考:https://www.jianshu.com/p/b99ec04fa483

查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. Linux的文件操作

    cd 改变当前文件目录 ls ll 显示出指定目录下所有的文件 文件的类型&#xff1a; -普通文件 d文件夹 l 软连接 mkdir 创建文件目录 mkdir -p a/b/c/d/e/f会自动创建文件父目录 mkdir -p lucky/{1234}ls 一次可以创建多个子目录 rmdir 删除文件夹 rmdir&#xff1a;failed to…...

    2024/4/19 20:57:27
  2. 聚合关系和组成关系

    一 聚合关系 聚合关系描述了一个较大的事务&#xff08;整体&#xff09;&#xff0c;是由较小事务&#xff08;部分&#xff09;组成的。 公司和部门、部门和员工之间是聚合关系。聚合关系用一个空心菱形表示&#xff0c;菱形要画在“较大的事务”一侧。 1 常见存在聚合关系…...

    2024/4/7 18:24:35
  3. Pytorch加载数据

    dir() 函数&#xff1a; 让我们知道工具箱以及工具箱中的分隔区有什么东西 help()函数&#xff1a; 让我们知道每个工具箱如何使用&#xff0c;工具的使用方法 Pytorch加载数据 先来一个小案例&#xff1a; 根据输入得到对应的labels 将数据集dataset放入项目的文件目录下。…...

    2024/4/15 18:40:10
  4. 支小蜜校园刷脸消费守护师生舌尖上的安全

    一、无卡消费 师生方便就餐 师生在就餐的时候通过人脸识别进行认证消费接算&#xff0c;不用担心自己没有带现金或者没有带饭卡、损毁、丢失等无法就餐的问题。 二、人脸识别 保障账户安全 刷脸就餐通过人脸识别技术精准的识别用户&#xff0c;只有本人可以完成就餐消费&…...

    2024/4/20 14:46:51
  5. CdTe / CdS / ZnS量子点在黄曲霉毒素B1免疫分析及抗体识别分子模型中的应用

    摘要&#xff1a;为了开发一种灵敏的黄曲霉毒素B1 (AFB1)免疫测定方法&#xff0c;筛选了一种分泌具有高亲和力的抗AFB1单克隆抗体的杂交瘤。合成了一种新型的CdTe / CdS / ZnS量子点&#xff0c;并将其与人工抗原偶联&#xff0c;用作简单的一步荧光免疫测定(FLISA)中的荧光探…...

    2024/4/14 0:12:50
  6. 以太网监控2022年应当关注的关键挑战和解决方案

    以太网连接可帮助企业进行关键通信&#xff0c;即使是轻微的中断也会引起用户抱怨或导致代价高昂的停机时间。最重要的是&#xff0c;网络规模越大&#xff0c;以太网网络就变得越复杂。由于以太网网络决定了大型网络基础设施的可用性和性能&#xff0c;因此在您的网络中部署以…...

    2024/4/16 19:56:09
  7. Python之正则表达式(re模块)

    本节内容 re模块介绍使用re模块的步骤re模块简单应用示例关于匹配对象的说明说说正则表达式字符串前的r前缀re模块综合应用实例参考文档提示&#xff1a; 由于该站对MARKDOWN的表格支持的不是很好&#xff0c;所以本文中的表格均以图片的形式提供&#xff0c;大家如果看着比较模…...

    2024/4/25 6:46:47
  8. CSPNET: A NEW BACKBONE THAT CAN ENHANCE LEARNING CAPABILITY OF CNN

    论文链接&#xff1a;https://arxiv.org/pdf/1911.11929.pdf 提出的CSPNet&#xff0c;使最先进的方法&#xff0c;如ResNet、ResNeXt和DenseNet成为移动gpu或cpu的轻量级方法。其中一个主要贡献是认识到冗余梯度信息问题&#xff0c;它导致低效的优化和昂贵的推理计算。故提出…...

    2024/4/19 12:04:58
  9. 利用Matplotlib画图

    - Matplotlib绘图基础 plt.plot(x,y,ls,lw,c,marker&#xff0c;markersize,markeredgecolor,markerfacecolor,label) - x:x轴上的数值 - y&#xff1a;y轴上的数值 - ls&#xff1a;折线的风格(-,--,-,,&#xff08;点画线&#xff09;:(实点线)) - lw&#xff1a…...

    2024/4/18 8:02:54
  10. 2022年锂电池行业-电池端预期反转、材料端逻辑分化(附下载)

    导读&#xff1a;展望2022年&#xff0c;全球新能源汽车销量有望达到900-1000万辆&#xff0c;中国新能源汽车销量有望突破500万辆。虽然芯片供需关系缓解可能会使得燃油车销量出现一定程度的复苏&#xff0c;但电动车下游高需求已被验证&#xff0c;整体渗透率持续上行趋势不改…...

    2024/4/14 0:13:10
  11. Python学习五:模块、__name__属性、dir() 函数、标准模块包、输出格式美化、读取键盘输入、读和写文件、文件对象的方法、pickle 模块、open() 方法、file 对象…

    模块 import 与 from...import参考Python学习一&#xff1a;基础语法&#xff08;保留字、注释、行与缩进、多行语句、空行、同一行显示多条、print输出、import%from...import&#xff09;、基本数据类型_岳小诺的博客-CSDN博客https://blog.csdn.net/weixin_54897533/articl…...

    2024/4/5 3:46:04
  12. 解决SpringCloud的Gateway网关无法访问服务的静态资源

    解决SpringCloud的Gateway网关无法访问服务的静态资源 在重构项目的时候&#xff0c;配置gateway网关后&#xff0c;利用网关地址访问服务&#xff0c;发现服务的静态资源报了404 报错如下&#xff1a; gateway网关配置如下&#xff1a; 一开始我觉得是在gateway网关配置的断…...

    2024/4/14 0:12:55
  13. c语言随机数猜数大小

    随机数的生成 头文件包含 stdio.h&#xff0c;stdlib.h以及time.h 。 rand()函数与srand()函数包含在头文件stdlib.h中&#xff0c;time()函数包含在头文件time.h中。 rand()函数功能为返回随机整数&#xff0c;范围在0至RAND_MAX 间。 srand()函数功能为随机数发生器的初始化。…...

    2024/4/14 0:13:20
  14. linux防火墙常用命令

    打开端口 注意要加上 --permanent [rootZ ~] firewall-cmd --zonepublic --add-port80/tcp --permanent success 重新加载 ,不执行这一步配置不会生效 [rootZ ~] firewall-cmd --reload success 查看已经开放的端口 [rootZ ~] firewall-cmd --list-port...

    2024/4/14 0:12:50
  15. 中国典型省份煤电转型优化潜力研究-煤控研究(附下载)

    导读&#xff1a;在落后产能淘汰方面&#xff0c;2020到 2035年期间&#xff0c;各省都有一定数量的落后某申产能。为保障电力安全&#xff0c;有些省份在逐步淘汰后会新建一定规模的高效煤电机组&#xff0c;或者以"上大压下"的形式实行"等量或减量替代"。…...

    2024/4/7 18:24:22
  16. 使用PYTHON完成剪刀石头布小游戏

    作为新加入的python小白&#xff0c;课堂遇到的题目在这分享下&#xff0c;不知道思路怎样&#xff0c;大佬多多指教。 原题目&#xff1a; # 练习4&#xff1a;1.随机生成个石头剪刀布 # 2.输入石头剪刀布 # 3.输入的石头随机生成的是剪刀&…...

    2024/4/14 0:12:55
  17. STM32F429系统架构,androidframework开发

    — 辅助内部 SRAM2 (16 KB) — 辅助内部 SRAM3 (64 KB)&#xff08;仅适用于 STM32F42xxx 和 STM32F43xxx 器件&#xff09; — AHB1 外设&#xff08;包括 AHB-APB 总线桥和 APB 外设&#xff09; — AHB2 外设 — FSMC借助总线矩阵&#xff0c;可以实现主控总线到被控总线…...

    2024/4/14 0:13:00
  18. Java基础——一切皆对象

    本文参考自Java编程思想第四版&#xff0c;并结合自己现有知识做的一些总结。 尽管Java是基于C的&#xff0c;但相比之下&#xff0c;Java是一种更为“纯粹”的面向对象程序设计语言。 Java语言假设我们只进行面向对象的程序设计&#xff0c;因为可以发现Java代码都是由一个接…...

    2024/4/14 0:13:25
  19. Windows和Linux系统开通FTP教程

    Windows环境 1.WindowsServer系统 首先在左下角开始菜单右键选择运行输入“dcomcnfg.exe”&#xff0c;打开组件服务管理。    展开组件服务&#xff0c;计算机&#xff0c;右击我的电脑&#xff0c;选择属性。    在默认属性选项卡中&#xff0c; 选择&#xff1a;- 勾…...

    2024/4/14 0:12:55
  20. 以空格分割字符串赋给数组(str可以是输入参数或echo)和read命令

    目录 1. 可以将变量或echo数据直接()赋给数组 2. read命令 1. 可以将变量或echo数据直接()赋给数组 下面分别将字符"1 2 3"和输入参数$1赋给了数组 设该脚本name为test_input2array.sh 运行命令为 ./test_input2array.sh "sd erg wef" sdgf #!/bi…...

    2024/4/14 0:13:15

最新文章

  1. 革新未来电子:科研团队利用氢键操控超薄磁性材料,开辟纳米科技新纪元

    德国亥姆霍兹德累斯顿-罗森多夫研究中心(Helmholtz-Zentrum Dresden-Rossendorf, HZDR)与德累斯顿工业大学(TUD)领导的一个德美研究团队&#xff0c;在《Nano Letters》期刊上提出了一项创新理念&#xff0c;旨在改善超薄材料的性能。该理念通过让二维(2D)材料与氢反应&#xf…...

    2024/5/3 14:41:12
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. golang 协程池 动态扩缩容

    参考 github.com/panjf2000/ants package mainimport ("fmt""sync""sync/atomic""time"_ "github.com/panjf2000/ants" )type pool struct {// 协程池最大容量cap int32// 当前运行的协程个数run int32block bool// 空闲…...

    2024/5/3 9:55:09
  4. redis之主从复制、哨兵模式

    一 redis群集有三种模式 主从复制&#xff1a; 主从复制是高可用Redis的基础&#xff0c;哨兵和集群都是在主从复制基础上实现高可用的。 主从复制主要实现了数据的多机备份&#xff0c;以及对于读操作的负载均衡和简单的故障恢复。 缺陷&#xff1a; 故障恢复无法自动化&…...

    2024/5/1 13:42:29
  5. 一场人生的风险控制,商业社会识人指南

    一、资料前言 本套社会识人资料&#xff0c;大小679.94M&#xff0c;共有37个文件。 二、资料目录 识人的终极目的&#xff1a;一整场人生的风险控制.pdf 信任的搭建&#xff1a;更多的时间与维度.pdf 没有搞不定的人&#xff01;角色人格与全面人格.pdf 政治不正确的正确…...

    2024/5/1 14:07:45
  6. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/5/1 17:30:59
  7. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/5/2 16:16:39
  8. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/4/29 2:29:43
  9. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/5/2 9:28:15
  10. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/4/27 17:58:04
  11. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/4/27 14:22:49
  12. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/4/28 1:28:33
  13. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/4/30 9:43:09
  14. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/4/27 17:59:30
  15. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/5/2 15:04:34
  16. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/4/28 1:34:08
  17. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/4/26 19:03:37
  18. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/4/29 20:46:55
  19. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/4/30 22:21:04
  20. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/5/1 4:32:01
  21. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/4/27 23:24:42
  22. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/4/28 5:48:52
  23. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/4/30 9:42:22
  24. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/5/2 9:07:46
  25. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/4/30 9:42:49
  26. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  27. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  28. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  29. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  30. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  31. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  32. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  33. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  34. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  35. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  36. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  37. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  38. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  39. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  40. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  41. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  42. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  43. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  44. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  45. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57