摘自 《Docker 容器与容器云(第2版)》

《Docker 容器与容器云(第2版)》
《Docker 容器与容器云(第2版)》

1. 从容器到容器云

1.1 云计算平台

经典云计算架构包括 IaaS(Infrastructure as a Service,基础设施即服务)、PaaS(Platform as a Service,平台即服务)、SaaS(Software as a Service,软件即服务)三层服务,如下图所示。

云平台经典架构
云平台经典架构

1.2 容器技术生态系统

容器技术生态系统
容器技术生态系统

可以看出,容器技术生态系统自上而下分别覆盖了 IaaS 层PaaS 层所涉及的各类问题,包括资源调度、编排、部署、监控、配置管理、存储网络管理、安全、容器化应用支撑平台等。容器技术主要带来了以下几点好处:

  • 持续部署与测试:容器消除了线上线下的环境差异,保证了应用生命周期的环境一致性标准化
  • 跨云平台支持:越来越多的云平台都已支持容器,用户无需担心受到云平台的捆绑。
  • 环境标准化和版本控制:可使用 Git 等工具对容器镜像进行版本控制。相比基于代码的版本控制来说,还能够对整个应用运行环境实现版本控制,一旦出现故障可以快速回滚。相比以前的虚拟机镜像,容器压缩和备份速度更快,镜像启动也像启动一个普通进程一样快速。
  • 高资源利用率与隔离:容器没有管理程序的额外开销,与底层共享操作系统,性能更优,负载更低。同时,容器拥有不错的资源隔离与限制能力,可以精确的对应用分配 CPU、内存等资源,保证应用之间不会相互影响。
  • 容器跨平台性与镜像:容器在原有 Linux 容器的基础上进行大胆革新,为容器设定了一整套标准化的配置方法,将应用及其依赖的运行环境打包成镜像,大大提高了容器的跨平台性
  • 易于理解且易用Docker 的英文原意是处理集装箱的码头工人,标志是鲸鱼运送一大堆集装箱,集装箱就是容器。容器的易用性加速了容器标准化的步伐。
  • 应用镜像仓库:Docker 官方构建了一个镜像仓库,已经累积了成千上万的镜像,所有人都可以自由的下载微服务组件,为开发者提供了巨大便利。

1.3 从容器到容器云

容器云以容器为资源分割和调度的基本单位,封装整个软件运行时环境,为开发者和系统管理员提供用于构建、发布和运行分布式应用的平台。

  • 当容器云专注于资源共享与隔离、容器编排与部署时,它更接近传统的 IaaS
  • 当容器云渗透到应用支撑与运行时环境时,它更接近传统的 PaaS

容器云并不仅限于 Docker,基于 rkt 容器的 CoreOS 项目也是容器云。Docker 最初发布时只是一个单机下的容器管理工具,随后 Docker 公司发布了 Compose、Machine、Swarm编排部署工具,并收购了 Socketplane 解决集群化后的网络问题

除了 Docker 公司之外,业界许多云计算厂商也对基于 Docker 的容器云做了巨大的投入。例如 Fleet、Flynn、Deis 以及目前成为事实主流标准的 Kubernetes,都是基于 Docker 技术构建的广为人知的容器云。

2. Docker 基础

2.1 Docker 的安装

安装 Docker基本要求如下:

  • 只支持 64 位 CPU 架构的计算机,目前不支持 32 位 CPU
  • 建议系统的 Linux 内核版本3.10及以上
  • Linux 内核需开启 cgroupsnamespace 功能

安装过程可参考 CentOS 7 安装 Docker CE

2.2 Docker 操作参数解读

docker命令的执行一般都需要 root 权限,因为 Docker 的命令行工具dockerDocker daemon 是同一个二进制文件,而 Docker daemon 负责接收并执行来自docker的命令,它的运行需要 root 权限。同时,从 Docker 0.5.2 版本开始,Docker daemon 默认绑定一个 UNIX Socket 来代替原有的 TCP 端口,该 UNIX Socket 默认是属于 root 用户的。

用户在使用 Docker 时,需要使用 Docker 命令行工具dockerDocker daemon 建立通信。Docker daemon 是 Docker 守护进程,负责接收并分发执行 Docker 命令。可以使用dockerdocker help命令获取docker的命令清单:

> docker

Usage:  docker [OPTIONS] COMMAND

A self-sufficient runtime for containers

Options:
      --config string      Location of client config files (default "/root/.docker")
  -D, --debug              Enable debug mode
  -H, --host list          Daemon socket(s) to connect to
  -l, --log-level string   Set the logging level ("debug"|"info"|"warn"|"error"|"fatal") (default "info")
      --tls                Use TLS; implied by --tlsverify
      --tlscacert string   Trust certs signed only by this CA (default "/root/.docker/ca.pem")
      --tlscert string     Path to TLS certificate file (default "/root/.docker/cert.pem")
      --tlskey string      Path to TLS key file (default "/root/.docker/key.pem")
      --tlsverify          Use TLS and verify the remote
  -v, --version            Print version information and quit

Management Commands:
  config      Manage Docker configs
  container   Manage containers
  image       Manage images
  network     Manage networks
  node        Manage Swarm nodes
  plugin      Manage plugins
  secret      Manage Docker secrets
  service     Manage services
  stack       Manage Docker stacks
  swarm       Manage Swarm
  system      Manage Docker
  trust       Manage trust on Docker images
  volume      Manage volumes

例如可以使用docker start --help命令来获取子命令start的详细信息:

> docker start --help

Usage:  docker start [OPTIONS] CONTAINER [CONTAINER...]

Start one or more stopped containers

Options:
  -a, --attach               Attach STDOUT/STDERR and forward signals
      --detach-keys string   Override the key sequence for detaching a container
  -i, --interactive          Attach container's STDIN

推荐阅读:

  1. docker专题(2):docker常用管理命令(上)| Sean’s Notes
  2. docker专题(2):docker常用管理命令(下)| Sean’s Notes

根据命令的用途,可将 Docker 子命令进行如下分类

Docker 子命令分类
Docker 子命令分类

docker命令的使用出发,可以梳理出如下的命令结构图

Docker 命令结构图
Docker 命令结构图

下面选择每个功能分类中常用的子命令进行用法和操作参数的解读。

1. Docker 环境信息

docker info命令用于检查 Docker 是否正确安装。如果 Docker 正确安装,该命令会输出 Docker 的配置信息

> docker info
Containers: 33
 Running: 20
 Paused: 0
 Stopped: 13
Images: 23
Server Version: 18.06.1-ce
Storage Driver: overlay2
...
Kernel Version: 4.15.0-38-generic
Operating System: Ubuntu 18.04.1 LTS
...

docker info命令一般结合docker version命令使用,两者结合能够提取到足够详细的 Docker 环境信息

> docker version
Client:
 Version:           18.06.1-ce
 API version:       1.38
 Go version:        go1.10.3
 Git commit:        e68fc7a
 Built:             Tue Aug 21 17:24:56 2018
 OS/Arch:           linux/amd64
 Experimental:      false

Server:
 Engine:
  Version:          18.06.1-ce
  API version:      1.38 (minimum version 1.12)
  Go version:       go1.10.3
  Git commit:       e68fc7a
  Built:            Tue Aug 21 17:23:21 2018
  OS/Arch:          linux/amd64
  Experimental:     false

2. 容器生命周期管理

容器生命周期管理涉及容器启动、停止等功能。

docker run 命令

docker run命令使用方法如下:

docker run [OPTIONS] IMAGE [COMMAND] [ARG...]

docker run命令用来基于特定的镜像创建一个容器,并依据选项来控制该容器:

> docker run ubuntu echo "Hello Docker"
Hello Docker

该命令从 ubuntu 镜像启动一个容器,并执行echo命令打印 Hello Docker。执行完echo命令后,容器将停止运行docker run命令启动的容器会随机分配一个容器 IDCONTAINER ID,用以标识该容器。

root@ubuntu:~> docker run -i -t --name mytest ubuntu:latest /bin/bash
root@eb9dda25b0fe:/>

上例中,docker run命令启动一个容器,并为它分配一个伪终端执行/bin/bash命令,用户可以在该伪终端与容器进行交互。其中:

  • -i:表示使用交互模式,始终保持输入流开放
  • -t:表示分配一个伪终端,一般两个参数结合时使用-it
  • --name:可以指定启动的容器的名字。若无此选项,Docker 将为容器随机分配一个名字
  • -c:用于给运行在容器中的所有进程分配 CPUshares 值,这是一个相对权重,实际处理速度还与宿主机的 CPU 有关
  • -m:用于限制为容器中所有进程分配的内存总量,以 B、K、M、G 为单位
  • -v:用于挂载一个 volume,可以用多个-v参数同时挂载多个 volume。volume 的格式为[host-dir]:[container-dir]:[rw|ro]
  • -p:用于将容器的端口暴露给宿主机的端口,其常用格式为hostPort:containerPort。这样外部主机就可以通过宿主机暴露的端口来访问容器内的应用
docker start/stop/restart 命令

对于已经存在的容器,可以通过docker start/stop/restart命令来启动、停止和重启,一般利用CONTAINER ID标识来确定具体容器,某些情况下也使用容器名来确定容器。

docker start命令使用-i选项来开启交互模式,并始终保持输入流开放。使用-a选项来附加标准输入、输出或错误输出。此外,docker stopdocker restart命令使用-t选项来设定容器停止前的等待时间

3. Docker registry

Docker registry存储容器镜像的仓库,用户可以通过 Docker clientDocker registry 进行通信,以此来完成镜像的搜索、下载和上传等相关操作。

Docker Hub 是 Docker 公司官方提供的镜像仓库,提供镜像的公有与私有存储服务,是目前最主要的镜像来源。除此之外,用户还可以自行搭建私有服务器来实现镜像仓库功能。

docker pull 命令

用于从 Docker registry 中拉取 imagerepository

docker pull [OPTIONS] NAME[:TAG @DIGEST]

使用示例如下:

# 从官方 Hub 拉取 ubuntu:latest 镜像
> docker pull ubuntu
# 从官方 Hub 拉取指明 "ubuntu 16.04" tag 的镜像
> docker pull ubuntu:16.04
# 从特定的仓库拉取 ubuntu 镜像
> docker pull SEL/ubuntu
# 从其他服务器拉取镜像
> docker pull 10.10.103.215:5000/sshd
docker push 命令

用于将本地的 imagerepository 推送到 Docker Hub 的公共或私有镜像库,以及私有服务器:

docker push [OPTIONS] NAME[:TAG]

使用示例如下:

> docker push SEL/ubuntu

4. 镜像管理

用户可以在本地保存镜像资源,为此 Docker 提供了相应的管理子命令。

docker images 命令

通过docker images命令可以列出主机上的镜像,默认只列出最顶层的镜像。使用-a选项可以显示所有镜像

docker images [OPTIONS] [REPOSITORY[:TAG]]

使用示例如下:

> docker images
REPOSITORY                    TAG                 IMAGE ID            CREATED             SIZE
ubuntu                        16.04               b0ef3016420a        11 days ago         117MB
influxdb                      latest              623f651910b3        7 weeks ago         238MB
memcached                     latest              8230c836a4b3        7 weeks ago         62.2MB
mongo                         3.2                 fb885d89ea5c        7 weeks ago         300MB
mist/mailmock                 latest              95c29bda552f        7 weeks ago         299MB
mist/docker-socat             latest              f00ed0eed13f        7 weeks ago         7.8MB
mistce/logstash               v3-3-1              0f90a36d12c8        2 months ago        730MB
mistce/api                    v3-3-1              4a21b676352f        2 months ago        705MB
mistce/nginx                  v3-3-1              4f55dd9b39e0        2 months ago        109MB
mistce/gocky                  v3-3-1              ee93caf66f70        2 months ago        440MB
mistce/elasticsearch-manage   v3-3-1              10a48b9ea0e1        2 months ago        65.8MB
mistce/ui                     v3-3-1              b8fdbe0ccb23        2 months ago        626MB
ubuntu-with-vi-dockerfile     latest              74ba87f80b96        2 months ago        169MB
ubuntu-with-vi                latest              9d2fac08719d        2 months ago        169MB
ubuntu                        latest              ea4c82dcd15a        2 months ago        85.8MB
centos                        latest              75835a67d134        3 months ago        200MB
hello-world                   latest              4ab4c602aa5e        4 months ago        1.84kB
elasticsearch                 5.6.10              73e6fdf8bd4f        4 months ago        486MB
mistce/landing                v3-3-1              b0e433749aa9        5 months ago        532MB
kibana                        5.6.10              bc661616b61c        5 months ago        389MB
hello-world                   <none>              2cb0d9787c4d        6 months ago        1.85kB
traefik                       v1.5                fde722950ccf        9 months ago        49.7MB
mist/swagger-ui               latest              0b5230f1b6c4        10 months ago       24.8MB
rabbitmq                      3.6.6-management    c74093aa9895        22 months ago       179MB

上例中,从REPOSITORY属性可以判断出镜像是来自于官方镜像、私人仓库还是私有服务器

docker rmi/rm 命令

docker rmi命令用于删除镜像docker rm命令用于删除容器。它们可以同时删除多个镜像或容器,也可以按条件来删除:

docker rm [OPTIONS] CONTAINER [CONTAINER...]
docker rmi [OPTIONS] IMAGE [IMAGE...]

使用docker rmi命令删除镜像时,如果已有基于该镜像启动的容器存在,则无法直接删除,需要首先删除启动的容器。当然,这两个子命令都提供了-f选项,可以强制删除存在容器的镜像或启动中的容器。

5. 容器运维操作

作为 Docker 的核心,容器的操作是重中之重,Docker 也为用户提供了丰富的容器运维操作命令。

docker attach 命令

docker attach命令可以连接到正在运行的容器,观察该容器的运行情况,或与容器的主进程进行交互

docker attach [OPTIONS] CONTAINER
docker inspect 命令

docker inspect命令可以查看镜像和容器的详细信息,默认会列出全部信息,可以通过--format参数来指定输出的模板格式,以便输出特定信息:

docker inspect [OPTIONS] CONTAINER|IMAGE [CONTAINER|IMAGE...]

具体示例如下:

> docker inspect --format='{{.NetworkSettings.IPAddress}}' ee36
172.17.0.8
docker ps 命令

docker ps命令可以查看容器的相关信息,默认只显示正在运行的容器的信息。可以查看到的信息包括CONTAINER IDNAMESIMAGESTATUS、容器启动后执行的COMMAND、创建时间CREATED和绑定开启的端口PORTS

docker ps [OPTIONS]

docker ps命令常用的选项有-a-l-a选项可以查看所有容器,包括停止的容器。-l选项则只查看最新创建的容器,包括不在运行中的容器。

> docker ps -a
CONTAINER ID        IMAGE                       COMMAND                 CREATED             STATUS                         PORTS               NAMES
8befe85aa9b2        ubuntu                      "/bin/bash"             4 minutes ago       Exited (0) 4 minutes ago                           elegant_hawking
eb9dda25b0fe        ubuntu:latest               "/bin/bash"             About an hour ago   Exited (0) About an hour ago                       mytest
33be0880de8a        ubuntu                      "echo 'Hello Docker'"   About an hour ago   Exited (0) About an hour ago                       loving_neumann
9dbd65001cc2        ubuntu                      "echo hello"            About an hour ago   Exited (0) About an hour ago                       zealous_mendeleev
ee10555e84be        hello-world                 "/hello"                About an hour ago   Exited (0) About an hour ago                       friendly_mestorf
4219345c98a0        ubuntu-with-vi-dockerfile   "/bin/bash"             2 months ago        Exited (0) 2 months ago                            ecstatic_wilson
7257b9828da4        centos                      "/bin/bash"             2 months ago        Exited (0) 2 months ago                            hopeful_chaplygin
26119a6e11bd        centos                      "/bin/bash"             2 months ago        Exited (0) 2 months ago                            brave_khorana
f48bc1339340        ubuntu-with-vi              "/bin/bash"             2 months ago        Exited (127) 2 months ago                          agitated_hugle
1abe6e7341ca        ubuntu                      "/bin/bash"             2 months ago        Exited (0) 2 months ago                            laughing_leavitt
5c5eabb13be4        hello-world                 "/hello"                2 months ago        Exited (0) 2 months ago                            eloquent_wiles
8f2f6854078c        2cb0d9787c4d                "/hello"                4 months ago        Exited (0) 4 months ago                            goofy_sinoussi

> docker ps -l
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS               NAMES
8befe85aa9b2        ubuntu              "/bin/bash"         6 minutes ago       Exited (0) 6 minutes ago                       elegant_hawking

6. 其他子命令

docker commit 命令

docker commit命令可以将一个容器固化为一个新的镜像

docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]

提交保存时,只能选用正在运行的容器来制作新的镜像。在制作特定镜像时,直接使用docker commit命令只是一个临时性的辅助命令,不推荐使用。官方建议通过docker build命令结合 Dockerfile 来创建和管理镜像。

docker events/history/logs 命令

docker events/history/logs 这 3 个命令用于查看 Docker 的系统日志信息。docker events命令会打印出实时的系统事件docker history命令会打印出指定镜像的历史版本信息,即构建该镜像的每一层镜像的命令记录docker logs命令会打印出容器中进程的运行日志

docker events [OPTIONS]
docker history [OPTIONS] IMAGE
docker logs [OPTIONS] CONTAINER

2.3 搭建第一个 Docker 应用栈

Docker设计理念是希望用户能够保证一个容器只运行一个进程,即只提供一种服务。通常情况下,用户需要利用多个容器,分别提供不同的服务,并在不同容器间互连通信,最后形成一个 Docker 集群,以实现特定的功能。

基于 Docker 集群构建的应用称为 Docker App Stack,即 Docker 应用栈

以下示例将在单台机器上利用 Docker 自带的命令行工具,搭建一个 Docker 应用栈,利用多个容器来组成一个特定的应用。

在开始搭建过程前,需要对所要搭建的应用栈进行简单的设计和描述:我们将搭建一个包含 6 个节点的 Docker 应用栈,其中包括 1 个代理节点、2 个 Web 应用节点、1 个主数据库节点及 2 个从数据库节点。应用栈具体结构如下图所示:

Docker 应用栈结构图
Docker 应用栈结构图

如图所示,HAProxy负载均衡代理节点。Redis 是非关系型的数据库,它由一个主数据库节点和两个从数据库节点组成。App 是应用,这里将使用 Python 语言、基于 Django 架构设计一个访问数据库的基础 Web 应用。

1. 获取应用栈各节点所需镜像

在搭建过程中,可以从 Docker Hub 获取现有可用的镜像,在这些镜像的基础上启动容器,按照需求进行修改来实现既定的功能。

> docker pull ubuntu
> docker pull django
> docker pull haproxy
> docker pull redis
> docker images

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
haproxy             latest              d23194a3929a        40 hours ago        72MB
redis               latest              5d2989ac9711        12 days ago         95MB
ubuntu              latest              1d9c17228a9e        12 days ago         86.7MB
django              latest              eb40dcf64078        2 years ago         436MB

2. 应用栈容器节点互联

鉴于在同一主机下搭建容器应用栈的环境,只需要完成容器互联来实现容器间的通信即可,可以采用docker run命令的--link选项建立容器间的互联关系。使用示例如下:

> docker run --link redis:redis --name console ubuntu bash

上例将在 ubuntu 镜像上启动一个容器,并命名为console,同时将新启动的console容器连接到名为redis的容器上

通过--link选项来建立容器间的连接,不但可以避免容器的 IP 和端口暴露到外网所导致的安全问题,还可以防止容器在重启后 IP 地址变化导致的访问失效,原理类似于 DNS 的域名和地址映射。

回到应用栈的搭建,应用栈各节点的连接信息如下:

  • 启动redis-master容器节点
  • 两个redis-slave容器节点启动时要连接到redis-master
  • 两个 App 容器节点启动时要连接到redis-master
  • HAProxy 容器节点启动时要连接到两个 App 节点上

综上所述,容器的启动顺序为:

redis-master --> redis-slave --> APP --> HAProxy

此外,为了能够从外网访问应用栈,并通过 HAProxy 节点来访问应用栈中的 App,在启动 HAProxy 容器节点时,需要利用-p参数暴露端口给主机,即可从外网访问搭建的应用栈。以下是整个应用栈的搭建流程示例。

3. 应用栈容器节点启动

# 启动 Redis 容器
> docker run -it --name redis-master redis /bin/bash
> docker run -it --name redis-slave1 --link redis-master:master redis /bin/bash
> docker run -it --name redis-slave2 --link redis-master:master redis /bin/bash

# 启动 Django 容器,即应用
> docker run -it --name APP1 --link redis-master:db -v ~/Projects/Django/App1:/usr/src/app django /bin/bash
> docker run -it --name APP2 --link redis-master:db -v ~/Projects/Django/App2:/usr/src/app django /bin/bash

# 启动 HAProxy 容器
> docker run -it --name HAProxy --link APP1:APP1 --link APP2:APP2 -p 6301:6301 -v ~/Projects/HAProxy:/tmp haproxy /bin/bash

启动的容器信息可以通过docker ps命令查看:

> docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
733e71e16ac5        haproxy             "/docker-entrypoint.…"   30 seconds ago      Up 29 seconds       0.0.0.0:6301->6301/tcp   HAProxy
3f91ac2a23a6        django              "/bin/bash"              47 seconds ago      Up 46 seconds                                APP2
e94c7ff2c319        django              "/bin/bash"              3 minutes ago       Up 3 minutes                                 APP1
5e7994e6ad59        redis               "docker-entrypoint.s…"   5 minutes ago       Up 4 minutes        6379/tcp                 redis-slave2
6fac6db730c3        redis               "docker-entrypoint.s…"   8 minutes ago       Up 8 minutes        6379/tcp                 redis-slave1
936c426faa29        redis               "docker-entrypoint.s…"   8 minutes ago       Up 8 minutes        6379/tcp                 redis-master

至此,所有搭建应用栈所需容器的启动工作已经完成。

4. 应用栈容器节点的配置

Redis Master 主数据库容器节点的配置

Redis Master 主数据库容器节点启动后,我们需要在容器中添加 Redis 的启动配置文件,以启动 Redis 数据库

由于容器的轻量化设计,其中缺乏相应的文本编辑命令工具,这时可以利用 volume 来实现文件的创建。在容器启动时,利用-v参数挂载 volume,在主机和容器之间共享数据,就可以直接在主机上创建和编辑相关文件

在利用 Redis 镜像启动容器时,镜像中已经集成了 volume 的挂载命令,通过docker inspect命令查看redis-master所挂载 volume 的情况

> docker inspect --format "{{.Mounts}}" redis-master
[{volume a77509a99df7d7a9d78313c1a1bb19619bac98fedadd78dbab17f072a49a905c /var/lib/docker/volumes/a77509a99df7d7a9d78313c1a1bb19619bac98fedadd78dbab17f072a49a905c/_data /data local  true }]

可以发现,该 volume 在主机中的目录/var/lib/docker/volumes/a77509a99df7d7a9d78313c1a1bb19619bac98fedadd78dbab17f072a49a905c/_data在容器中的目录/data。进入主机目录创建 Redis 的启动配置文件

> cd /var/lib/docker/volumes/a77509a99df7d7a9d78313c1a1bb19619bac98fedadd78dbab17f072a49a905c/_data
> cp <your-own-redis-dir>/redis.conf redis.conf
> vim redis.conf

对于 Redis 主数据库,需要修改模板文件中的如下几个参数:

daemonize yes
pidfile /var/run/redis.pid
protected-mode no # 关闭保护模式

在主机创建好启动配置文件后,切换到容器中的 volume 目录,并复制redis.conf到 Redis 的执行工作目录,然后启动 Redis 服务器

> cd /data
> cp redis.conf /usr/local/bin/
> cd /usr/local/bin/
> redis-server redis.conf
Redis Slave 从数据库容器节点的配置

redis-master容器节点类似,在启动redis-slave容器节点后,首先需要查看 volume 信息,然后redis.conf复制到对应的目录中。不同的是,对于 Redis 从数据库,需要修改如下几个参数:

daemonize yes
pidfile /var/run/redis.pid
protected-mode no # 关闭保护模式
replicaof master 6379 # 之前是 slaveof

replicaof参数的使用格式replicaof <masterip> <masterport>

在主机修改好redis.conf配置文件后,切换到容器中的/data目录,并复制配置文件到 Redis 的执行工作目录,然后启动 Redis 服务器

> cd /data
> cp redis.conf /usr/local/bin/
> cd /usr/local/bin/
> redis-server redis.conf
594:C 10 Jan 2019 23:10:43.936 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
594:C 10 Jan 2019 23:10:43.936 # Redis version=5.0.3, bits=64, commit=00000000, modified=0, pid=594, just started
594:C 10 Jan 2019 23:10:43.936 # Configuration loaded

同理,可以完成对另一个 Redis Slave 容器节点的配置。至此,便完成了所有 Redis 数据库容器节点的配置

Redis 数据库容器节点的测试

完成 Redis MasterRedis Slave 容器节点的配置以及服务器的启动后,可以通过启动redis-cli来测试数据库。

首先,在redis-master容器内,启动redis-cli,并存储一个数据:

> redis-cli
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=172.17.0.3,port=6379,state=online,offset=1260,lag=0
slave1:ip=172.17.0.4,port=6379,state=online,offset=1260,lag=0
master_replid:295c948cc1bbdf21eb49fdd8417ba5b4b76fc32b
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:1260
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:1260
127.0.0.1:6379> set master 936c
OK
127.0.0.1:6379> get master
"936c"

随后,在redis-slave1redis-slave2两个容器中,分别启动redis-cli查询先前在redis-master数据库中存储的数据

> redis-cli
127.0.0.1:6379> info replication
# Replication
role:slave
master_host:master
master_port:6379
master_link_status:up
master_last_io_seconds_ago:3
master_sync_in_progress:0
slave_repl_offset:1330
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:295c948cc1bbdf21eb49fdd8417ba5b4b76fc32b
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:1330
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:127
repl_backlog_histlen:1204
127.0.0.1:6379> get master
"936c"

可以看到redis-master主数据库中的数据已经自动同步到了两个从数据库中。至此,应用栈的数据库部分已搭建完成,并通过测试。

APP 容器节点( Django)的配置

Django 容器启动后,需要利用 Django 框架,开发一个简单的 Web 程序。

为了访问数据库,需要在容器中安装 Python 语言的 Redis 支持包

> pip install redis

安装完成后,验证 Redis 支持包是否安装成功

> python
Python 3.4.5 (default, Dec 14 2016, 18:54:20) 
[GCC 4.9.2] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import redis
>>> print(redis.__file__)
/usr/local/lib/python3.4/site-packages/redis/__init__.py

如果没有报错,就说明已经可以使用 Python 语言来调用 Redis 数据库。接下来开始创建 Web 程序。以APP1为例,首先在容器的 volume 目录/usr/src/app/下创建 APP

# 在容器内
> cd /usr/src/app/
> mkdir dockerweb
> cd dockerweb/
> django-admin.py startproject redisweb
> ls
redisweb
> cd redisweb
> ls
manage.py redisweb
> python manage.py startapp helloworld
> ls
helloworld manage.py redisweb

在容器内创建好 APP 后,切换到主机的 volume 目录~/Projects/Django/App1进行相应的编辑来配置 APP

# 在主机内
> cd ~/Projects/Django/App1
> ls
dockerweb

可以看到,在容器内创建的 APP 文件在主机的 volume 目录下同样可见。之后修改helloworld应用的视图文件views.py

> cd dockerweb/redisweb/helloworld
> ls
admin.py  __init__.py  models.py  views.py  apps.py   migrations  tests.py
> vim views.py

为了简化设计,只要求完成 Redis 数据库信息输出,以及从 Redis 数据库存储和读取数据的结果输出viwes.py文件如下:

from django.shortcuts import render
from django.http import HttpResponse

# Create your views here.
import redis

def hello(request):
    str = redis.__file__
    str += "<br>"
    r = redis.Redis(host="db", port=6379, db=0)
    info = r.info()
    str += ("Set Hi <br>")
    r.set('Hi', 'HelloWorld-APP1')
    str += ("Get Hi: %s <br>" % r.get('Hi'))
    str += ("Redis Info: <br>")
    str += ("Key: Info Value")
    for key in info:
        str += ("%s: %s <br>" % (key, info[key]))
    return HttpResponse(str)

完成views.py文件修改后,接下来修改redisweb项目的配置文件setting.py,并添加新建的helloworld应用

> cd ../redisweb/
> ls
__init__.py  __pycache__  settings.py  urls.py  wsgi.py
> vim settings.py

settings.py文件中的INSTALLED_APPS选项下添加 helloworld,并修改ALLOWED_HOSTS

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = ['*']


# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'helloworld'
]

此处为了演示方便将ALLOWED_HOSTS设置为['*']允许所有连接,在实际开发环境中请勿按此设置。另外在生产环境中还需DEBUG选项设置为False

最后,修改redisweb项目的 URL 模式文件urls.py,它将设置访问应用的 URL 模式,并为 URL 模式调用视图函数之间的映射表

> vim urls.py

urls.py文件中,引入 helloworld 应用的hello视图,并hello视图添加一个urlpatterns变量urls.py文件内容如下:

from django.conf.urls import url
from django.contrib import admin
from helloworld.views import hello

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^helloworld$', hello),
]

在主机下修改完成这几个文件后,需要再次进入APP1容器,在目录/usr/src/app/dockerweb/redisweb完成项目的生成

> python manage.py makemigrations
No changes detected
> python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying sessions.0001_initial... OK
> python manage.py createsuperuser
Username (leave blank to use 'root'): admin
Email address: admin@gmail.com
Password: 
Password (again): 
Superuser created successfully.

旧版本的 Django 使用syncdb命令来同步数据库并创建admin账户。在新版 Django 中syncdb命令已被移除,使用createsuperuser命令创建管理员账户

至此,APP1容器的所有配置已经完成,另一个APP2容器配置也是同样的过程,这样就完成了应用栈 APP 部分的全部配置

在启动 APP 的 Web 服务器时,可以指定服务器的端口和 IP 地址。为了通过 HAProxy 容器节点接受外网所有的公共 IP 地址访问,实现负载均衡,需要指定服务器的 IP 地址和端口。对于APP1使用 8001 端口,而APP2则使用 8002 端口。同时,都使用0.0.0.0地址。以APP1为例,启动服务器的过程如下:

> python manage.py runserver 0.0.0.0:8001
Performing system checks...

System check identified no issues (0 silenced).
January 11, 2019 - 03:35:58
Django version 1.10.4, using settings 'redisweb.settings'
Starting development server at http://0.0.0.0:8001/
Quit the server with CONTROL-C.
[11/Jan/2019 03:37:01] "GET /helloworld HTTP/1.1" 200 3999
[11/Jan/2019 03:37:14] "GET /admin/ HTTP/1.1" 200 2779
...
HAProxy 容器节点的配置

在完成了数据库和 APP 部分的应用栈部署后,最后部署一个 HAProxy 负载均衡代理的容器节点,所有对应用栈的访问将通过它来实现负载均衡

首先,将 HAProxy 的启动配置文件复制进容器中。在主机的 volume 目录~/Projects/HAProxy下,执行以下命令:

> cd ~/Projects/HAProxy
> vim haproxy.cfg

其中,haproxy.cfg配置文件的内容如下:

global
    log 127.0.0.1  local0  # 日志输入配置,所有日志都记录在本机,通过 local0 输出
    maxconn 4096   # 最大连接数
    chroot /usr/local/sbin # 改变当前工作目录
    daemon         # 以后台形式运行 HAProxy 实例
    nbproc 4       # 启动 4 个 HAProxy 实例
    pidfile /usr/local/sbin/haproxy.pid  # pid 文件位置

defaults
    log      127.0.0.1   local3   # 日志文件的输出定向
    mode     http           # { tcp|http|health } 设定启动实例的协议类型
    option   dontlognull    # 保证 HAProxy 不记录上级负载均衡发送过来的用于检测状态没有数据的心跳包
    option   redispatch     # 当 serverId 对应的服务器挂掉后,强制定向到其他健康>的服务器
    retries  2              # 重试 2 次连接失败就认为服务器不可用,主要通过后面的 check 检查
    maxconn  2000           # 最大连接数
    balance roundrobin      # balance 有两个可用选项:roundrobin 和 source,其中,roundrobin 表示
                            # 轮询,而 source 表示 HAProxy 不采用轮询的策略,而是把来自某个 IP 的请求转发给一个固定 IP 的后端
    timeout connect 5000ms  # 连接超时时间
    timeout client 50000ms  # 客户端连接超时时间
    timeout server 50000ms  # 服务器端连接超时时间

listen redis_proxy 
    bind 0.0.0.0:6301
    stats enable
    stats uri /haproxy-stats
        server APP1 APP1:8001 check inter 2000 rise 2 fall 5  # 你的均衡节点
        server APP2 aPP2:8002 check inter 2000 rise 2 fall 5

随后,进入到容器的 volume 目录/tmp下,将 HAProxy启动配置文件复制到 HAProxy工作目录中:

# 在容器中
> cd /tmp
> cp haproxy.cfg /usr/local/sbin/
> cd /usr/local/sbin/
> ls
haproxy  haproxy.cfg

接下来利用该配置文件来启动 HAProxy 代理

> haproxy -f haproxy.cfg

另外,如果修改了配置文件的内容,需要先结束所有的 HAProxy 进程,并重新启动代理。Docker 镜像为了精简体积,本身并没有安装pskillall进程管理命令,需要手动在容器中安装

> apt-get update
> apt-get install procps # ps、pkill
> apt-get install psmisc # killall
> killall haproxy

至此,完成了 HAProxy 容器节点的全部部署,同时也完成了整个 Docker 应用栈的部署

应用栈访问测试

参考结构图可知,整个应用栈群的访问是通过 HAProxy 代理节点来进行的。HAProxy 在启动时通过-p 6301:6301参数,映射了容器访问的端口到主机上,因此可在其他主机上通过本地主机的 IP 地址和端口来访问搭建好的应用栈。

首先在本地主机上进行测试。在浏览器中访问http://172.17.0.7:6301/helloworld,可以查看来自 APP1 或 APP2 的页面内容,具体访问到的 APP 容器节点会由 HAProxy 代理进行均衡分配。其中,172.17.0.7HAProxy 容器的 IP 地址

访问 APP1 容器节点
访问 APP1 容器节点
访问 APP2 容器节点
访问 APP2 容器节点

本地测试通过后,尝试在其他主机上通过应用栈入口主机的 IP 地址暴露的 6301 端口来访问该应用栈,即访问http://116.56.129.153:6301/helloworld,可看到来自 APP1 或 APP2 容器节点的页面,访问http://116.56.129.153:6301/haproxy-stats
则可看到 HAProxy 的后台管理页面及统计数据。其中,116.56.129.153宿主机的 IP 地址

其他主机访问本地主机
其他主机访问本地主机
HAProxy 后台管理页面
HAProxy 后台管理页面