Manning.Learn.Docker.in.a.Month.of.Lunches.2020.6-笔记,关联 docker-compose

例子

https://github.com/microservices-demo

It’s a great sample application if you want to see how microservices are actually implemented. Each component owns its own data and exposes it through an API.

serverless framework

If you’re running in the datacenter, you can host your own platform in Docker using Nuclio, OpenFaaS, or Fn Project, which are all popular open source serverless frameworks.

安装

Linux 安装

Docker 安装脚本 : https://get.docker.com/
Docker Engine : https://docs.docker.com/engine/install/
Docker Compose : https://docs.docker.com/compose/install/

  • arch linux / manjaro
1
sudo pacman -S docker docker-compose
  • ubuntu

https://docs.docker.com/engine/install/ubuntu/

图形化管理后台

  • Portainer : Open source
  • UCP(Universal Control Plane) : commercial

配置

查看 docker 版本

docker version 可以查看 docker Engine Client & Server 的版本信息。

如果看不到 Docker Engine Server,看下service 状态并启动

1
2
3
systemctl status docker.service
sudo systemctl start docker.service
sudo docker version

docker-compose version 可以看到 docker compose 版本信息。

container 管理

  • 容器中的app process 运行,容器运行; app process 退出,容器也退出。
  • 停止的容器,不会自动被删除,只能用户自己手动删除。

启动容器

1
2
# 启动执行目标命令,执行完退出
docker container run diamol/ch02-hello-diamol
1
2
3
4
5
# 通过命令行访问 container
# 执行 `exit` 退出交互命令行,同时 container 也停止。
docker container run --interactive --tty diamol/base
# 或
docker container run -it diamol/base
1
2
3
4
5
# --detach : 命令执行完,container 以 service 方式运行,不退出。
# --publish : 端口映射,宿主机的8088 映射 container 的 80 端口
docker container run --detach --publish 8088:80 diamol/ch02-hello-diamol-web
# 或
docker container run -d -p 8088:80 diamol/ch02-hello-diamol-web

带参数启动

1
2
3
docker container run --env TARGET=google.com diamol/ch03-web-ping
# 或
docker container run -e TARGET=docker.com -e INTERVAL=5000 web-ping

在 container 中执行命令

1
2
3
4
docker container exec <container-id>  ls /usr/local/apache2/htdocs
# 或 windows 下用 dir 代替 ls
docker container exec <container-id> cmd /s /c dir C:\usr\local\apache2\htdocs
index.html

向 container 中拷贝本地文件

1
docker container cp index.html <container-id>:/usr/local/apache2/htdocs/index.html

停止的容器

docker container stop <container-id>

删除容器

docker container rm <container-id>

1
2
# 删除所有容器
docker container rm --force $(docker container ls --all --quiet)

状态查看 docker ps 或 docker container ls

docker ps 等价于 docker container ls

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 当前正在运行的container
docker ps
# or
docker container ls

# 所有的container ,无论是否正在运行
docker ps -a
docker container ls -a
docker container ls --all

# -q 参数,表示只打印id
docker ps -q

# 所有停止运行的container
docker ps --filter "status=exited"
  • the container ID is the same as the hostname inside the container.

容器的详细情况 docker container inspect

1
docker container inspect <container-id>

容器运行的进程 docker container top

1
docker container top <container-id>

容器执行的日志 docker container logs

1
docker container logs <container-id>

docker container stats

显示 container 当前占用的 CPU、内存、网络等统计信息。

1
docker container stats <container-id>

image 镜像管理

镜像库服务器

镜像库服务器,称为,registries

Docker Hub 是公用镜像库, 地址是 https://hub.docker.com/

从 Docker Hub 下载 image

1
docker image pull diamol/ch03-web-ping

image 大小

1
2
3
4
5
6
docker image ls -f reference=diamol/golang -f reference=image-gallery

# output
REPOSITORY    TAG    IMAGE ID    CREATED    SIZE
image-gallery    latest    b41869f5d153    20 minutes ago    25.3MB
diamol/golang    latest    ad57f5c226fc    2 hours ago    774MB

使用 Dockerfile 创建 image

简单的例子

  • Dockerfile
1
2
3
4
5
6
7
FROM diamol/node
ENV TARGET="blog.sixeyed.com"
ENV METHOD="HEAD"
ENV INTERVAL="3000"
WORKDIR /web-ping
COPY app.js .
CMD ["node", "/web-ping/app.js"]

WORKDIR,创建这个目录,并作为工作目录。
COPY , 将本地文件或文件夹,拷贝到image文件系统中。

  • 目录结构
1
2
3
4
web-ping
├── app.js
├── Dockerfile
└── README.md
  • build image
1
2
3
4
5
cd /some-path/web-ping

# --tag 参数指定 image 的 name
# 最后一个参数,说明build 的 local 目录,点表示当前目录
docker image build --tag web-ping .

build好的image,被存放在 local image cache

1
2
# 在本地 image缓存中,查看w字母开头的image
docker image ls 'w*'

注意 docker 使用 image layer 来复用底层 image,Every Dockerfile instruction results in an image layer,所以 Dockerfile 中语句的顺序,应该注意,将经常更新改动的部分,放在最后。

例如:

1
2
3
4
5
6
7
8
9
10
FROM diamol/node

CMD ["node", "/web-ping/app.js"]

ENV TARGET="blog.sixeyed.com" \
    METHOD="HEAD" \
    INTERVAL="3000"

WORKDIR /web-ping
COPY app.js .
1
docker image build -t web-ping:v2 .

Multi-stage Dockerfile

例子:

1
2
3
4
5
6
7
8
FROM diamol/base AS build-stage
RUN echo 'Building...' > /build.txt
FROM diamol/base AS test-stage
COPY --from=build-stage /build.txt /build.txt
RUN echo 'Testing...' >> /build.txt
FROM diamol/base
COPY --from=test-stage /build.txt /build.txt
CMD cat /build.txt

上例有3个stage: build-stage, test-stage, and the final unnamed stage

实际最后的image,还是 final stage 生成。

每个stage 独立运行,但是可以从前一个stage 拷贝文件。

任何一个 stage fails, build fails。

编译 java app 的 Dockerfile 例子

1
2
3
4
5
6
7
8
9
10
11
12
13
FROM diamol/maven AS builder
WORKDIR /usr/src/iotd
COPY pom.xml .
RUN mvn -B dependency:go-offline
COPY . .
RUN mvn package

# app
FROM diamol/openjdk
WORKDIR /app
COPY --from=builder /usr/src/iotd/target/iotd-service-0.1.0.jar .
EXPOSE 80
ENTRYPOINT ["java", "-jar", "/app/iotd-service-0.1.0.jar"]

不需要编译的app Dockerfile 例子,以 Nodejs 为例

1
2
3
4
5
6
7
8
9
10
11
FROM diamol/node AS builder
WORKDIR /src
COPY src/package.json .
RUN npm install
# app
FROM diamol/node
EXPOSE 80
CMD ["node", "server.js"]
WORKDIR /app
COPY --from=builder /src/node_modules/ /app/node_modules/
COPY src/ .

Golang 的 Dockerfile 例子

1
2
3
4
5
6
7
8
9
10
11
12
FROM diamol/golang AS builder
COPY main.go .
RUN go build -o /server
# app
FROM diamol/base
ENV IMAGE_API_URL="http://iotd/image" \
ACCESS_API_URL="http://accesslog/access-log"
CMD ["/web/server"]
WORKDIR web
COPY index.html .
COPY --from=builder /server .
RUN chmod +x server

diamol/golang 有700多M,只是在编译的时候需要,不需要放在最终的image中。满足最小运行条件的 diamol/base 其实很小,只有20+M。

执行命令的指令: RUN、CMD、ENTRYPOINT

  • refer
  • RUN 指令
    • RUN 执行命令并创建新的镜像层,RUN 经常用于安装软件包。
  • CMD命令
    • CMD 指令允许用户指定容器的 默认执行的命令
    • 此命令会在容器启动且 docker run 没有指定其他命令时运行。
    • 如果 docker run 指定了其他命令,CMD 指定的默认命令将被忽略。
    • 如果 Dockerfile 中有多个 CMD 指令,只有最后一个 CMD 有效
  • CMD 有三种格式:

    • Exec 格式:CMD ["executable","param1","param2"]
      这是 CMD 的推荐格式。
    • CMD ["param1","param2"] 为 ENTRYPOINT 提供额外的参数,此时 ENTRYPOINT 必须使用 * * Exec 格式。
    • Shell 格式:CMD command param1 param2
  • ENTRYPOINT
    • 与CMD类似,但 ENTRYPOINT 不会被忽略,一定会被执行,即使运行 docker run 时指定了其他命令。
  • ENTRYPOINT 有两种格式:

    • Exec 格式:ENTRYPOINT ["executable", "param1", "param2"] 这是 ENTRYPOINT 的推荐格式。
      Exec 格式用于设置要执行的命令及其参数,同时可通过 CMD 提供额外的参数。
      ENTRYPOINT 中的参数始终会被使用,而 CMD 的额外参数可以在容器启动时动态替换掉。
    • Shell 格式:ENTRYPOINT command param1 param2
      Shell 格式会忽略任何 CMD 或 docker run 提供的参数。

镜像库 / sharing images / Docker Hub and other registries

镜像名称,aka. image reference

例子: docker.io/diamol/golang:latest

  • docker.io 镜像库的domain,默认是 docker hub的域名docker.io
  • diamol 镜像作者或组织
  • golang 镜像名称
  • latest 镜像的tag,用来作为版本或变体标识,默认是 latest

push image

先要设置 docker id ,即docker.com 上的用户名,而非email。

1
2
3
4
5
# using PowerShell on Windows
$dockerId="<your-docker-id-goes-here>"

# using Bash on Linux or Mac
export dockerId="<your-docker-id-goes-here>"

login docker.com

1
docker login --username $dockerId

为已存在的 image 创建reference,并tag

1
2
3
4
5
docker image tag image-gallery $dockerId/image-gallery:v1

# List the image-gallery image references:
docker image ls --filter reference=image-gallery --filter
reference='*/image-gallery'

自此,(1)使用了docker login 完成登陆;(2)有一个在 docker id 名下的 image

push 到 docker hub 上去:

1
docker image push $dockerId/image-gallery:v1

给镜像打 tag

一种用法是以3位版本号,如 [major].[minor].[patch],给镜像打 tag 。

例如:

1
2
3
4
docker image tag image-gallery registry.local:5000/gallery/ui:latest
docker image tag image-gallery registry.local:5000/gallery/ui:2
docker image tag image-gallery registry.local:5000/gallery/ui:2.1
docker image tag image-gallery registry.local:5000/gallery/ui:2.1.106

其他开源 docker registries

  • Docker 轻量级的 registry server
    https://github.com/distribution/distribution
    没有web ui 管理界面,支持 pull 和 push
  • VMWare 的 Harbor
  • CNCF Harbor Project
  • Sonatype Nexus Repository OSS
    https://www.sonatype.com/products/repository-oss-vs-pro-features

搭建自己的镜像库

docker image 默认之允许 https 连接,自己的registry一般都是http,所以要设置docker运行http。

方法(1) Docker Desktop 上的设置方法:

  1. 任务栏上的鲸鱼icon,右键菜单 > Settings 或 Preferences
  2. Daemon 标签页,在 insecure registries 列表中添加允许http的地址,例如,registry.local:5000
  3. 重启 Docker Engine

方法(2)直接修改 daemon.json

daemon.json 位置:

  • Windows : C:\ProgramData\docker\config
  • Linux : /etc/docker
1
2
3
4
5
{
  "insecure-registries": [
    "registry.local:5000"
  ]
}

然后重启Docker Engine。

重启后,可以用 docker info 查看配置效果。

网络管理

DNS

Docker has its own DNS service built in.

container 先使用 Docker 自建的DNS 查询域名,查不到,再开始外部的 DNS lookup。

可以在container 中执行 nslookup container-name 查看对应的docker内网IP。

对于有个多个IP关联的 container, Docker DNS 会每次变动下IP列表的顺序,做一个简单的负载均衡。

创建 NAT 网络

1
2
# network-name ,默认创建的叫 nat
docker network create <network-name>

启动容器时,--network 指定接入的网络。

1
2
docker container run --name iotd -d -p 800:80 --network nat image-of-
the-day

存储 / docker volumes & mount

container 默认文件系统

从docker 容器和本地电脑,相互拷贝文件:

1
2
3
4
5
# docker to local
docker container cp docker-name:/some/path/somefile somefile

# local to docker
docker container cp somefile docker-name:/some/path/somefile

每个容器都有自己的文件系统,在容器中以单独磁盘形式出现,如,/dev/sda1C:\

The writable layer is unique to each container.

The container writable layer is created by Docker when the container is started, and it’s deleted by Docker when the container is removed.

Stopping a container doesn’t automatically remove the writable layer.

Docker volumes

A docker volume is a unit of storage, exists independently of containers, and has it’s own life cycle.

Docker Volumnes can be attached to containers.

2种创建volume的方法:

  1. 使用 docker volume create 命令
  2. 在 dockerfile 中使用语法 VOLUME <target-directory>

例子,dockerfile 创建volume

运行容器的时候,自动生成一个volume,挂载到容器的 /data 目录

1
2
3
4
5
FROM diamol/dotnet-aspnet
WORKDIR /app
ENTRYPOINT ["dotnet", "ToDoList.dll"]
VOLUME /data
COPY --from=builder /out/ .
1
2
3
4
5
6
7
8
9
10
11
# 运行容器todo1,同时自动创建volume挂载到 /data
docker container run --name todo1 -d -p 8010:80 diamol/ch06-todo-list

# 列出 todo1 容器的volume
docker container inspect --format '\{\{.Mounts\}\}' todo1

# 列出所有 volume
docker volume ls

# 其他容器共用volume
docker container run --name todo3 -d --volumes-from todo1 diamol/ch06-todo-list

docker volume create 创建一个volume,在容器之间使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 容器中 volume 的挂载点
target = "data"       # for linux containers
$target = "c:\data"   # for Windows containers

# create a named volume
docker volune create todo-list

# v1 app ,使用 todo-list volume
docker container run -d -p 8011:80 -v todo-list:$target --name todo-v1 diamol/ch06-todo-list

# 删除 v1 app container
docker container rm -f todo-v1

# v2 app ,接着使用 todo-list volume
docker container run -d -p 8011:80 -v todo-list:$target --name todo-v2 diamol/ch06-todo-list:v2

-v --volume 参数指定的volume 目标目录会覆盖镜像原有的目录。 作为镜像作者,写dockerfile时候可以使用 VOLUME 命令指定下默认的volume,以防用户运行容器时,没有使用volume参数。

bind mounts

A bind mount makes a directory on the host available as a path on a container. 通过这个方法,host 和 container 可以直接相互访问文件。

示例:

1
2
3
4
5
6
$source="$(pwd)\database".ToLower(); $target="c:\data"     # Windows
source="$(pwd)/database" && target='/data'                 # Linux

mkdir ./database

docker container run --mount type=bind,source=$source,target=$target -d -p 8012:80 diamol/ch06-todo-list

只读方式mount,加readonly

1
2
3
4
5
6
7
8
9
10
11
12
13
14
cd ./ch06/exercises/todo-list

# save the source path as a variable:
$source="$(pwd)\config".ToLower(); $target="c:\app\config"   # Windows
source="$(pwd)/config" && target='/app/config'            # Linux

# run the container using the mount:
docker container run --name todo-configured -d -p 8013:80 --mount type=bind,source=$source,target=$target,readonly diamol/ch06-todo-list

# check the application:
curl http://localhost:8013

# and the container logs:
docker container logs todo-configured

局限性:

  • Linux可以针对单个文件mount,Windows不可以。
  • mount的如果是分布式文件系统,而这个文件系统不支持某些文件操作,app可能崩溃。
    例如,Postgres数据库使用了 Azure Files。Azure Files支持正常读写,但是不支持创建hard link,Postgres数据尝试创建file link,结果就崩溃了。

Docker Compose

Docker Compose 用来将隶属于一个app的多个容器,协同部署。

Docker Compose file docker-compose.yml 使用 YAML语法,用来指导 compose 工具部署容器。

docker-compose up 的简单例子

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
version: '3.7'

services:
  todo-web:
    image: diamol/ch06-todo-list
    ports:
      - "8020:80"
    networks:
      - app-net
networks:
  app-net:
    external:
      name: nat

version 表明 compose file format 的版本
external 表明使用外部已经创建好的网络,而不用compose重新创建。

运行:

1
2
docker network create nat
docker-compose up --detach

docker-compose 命令会在当前目录下读取 docker-compose.yml

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
accesslog:
  image: diamol/ch04-access-log
iotd:
  image: diamol/ch04-image-of-the-day
  ports:
    - "80"
image-gallery:
  image: diamol/ch04-image-gallery
  ports:
    - "8010:80"
  depends_on:
    - accesslog
    - iotd

docker-compose-viz

docker-compose-viz 工具可以根据当前目录的 docker-compose.yml 生成一个组件依赖关系图。

https://github.com/pmsipilot/docker-compose-viz

docker-compose 常用命令

docker-compose 不带任何参数,会显示帮助,列出可用子命令列表。

1
2
3
4
5
# 重复启动一个镜像的多个容器,下例启动3个
docker-compose up -d --scale service-name=3

# 查看最后一个service的log
docker-compose logs --tail=1 iotd
1
2
3
4
5
6
7
8
# 停止container,但是不从文件系统删除container
docker-compose stop

# 将停止的container重新启动
docker-compose start

# 停止,并且删除之前创建的container
docker-compose down

override files 继承和覆盖配置

  • docker-compose.yml
    The core Compose file, specifies services and settings that apply in every environment.
  • docker-compose-dev.yml
    The dev override file adds some specific settings for development.
  • docker-compose-test.yml
    The test override file adds some specific settings for testing.

简单的例子,

1
2
3
4
5
6
7
8
9
10
# from docker-compose.yml - the core app specification:
services:
  todo-web:
    image: diamol/ch06-todo-list
    ports:
      - 80
    environment:
      - Database:Provider=Sqlite
    networks:
      - app-net
1
2
3
4
# and from docker-compose-v2.yml - the version override file:
services:
  todo-web:
    image: diamol/ch06-todo-list:v2

config 命令不实际部署app,只是校验下配置。

1
docker-compose -f ./todo-list/docker-compose.yml -f ./todo-list/docker-compose-v2.yml config

一个展示override策略的例子

1
2
3
4
5
6
7
8
9
10
11
# remove any existing containers
docker container rm -f $(docker container ls -aq)

# run the app in dev configuration:
docker-compose -f ./numbers/docker-compose.yml -f ./numbers/docker-compose-dev.yml -p numbers-dev up -d

# and the test setup:
docker-compose -f ./numbers/docker-compose.yml -f ./numbers/docker-compose-test.yml -p numbers-test up -d

# and UAT(User Acceptance test):
docker-compose -f ./numbers/docker-compose.yml -f ./numbers/docker-compose-uat.yml -p numbers-uat up -d

关闭app

1
2
3
4
5
6
7
8
# this would work if we'd used the default docker-compose.yml file:
docker-compose down

# this would work if we'd used override files without a project name:
docker-compose -f ./numbers/docker-compose.yml -f ./numbers/docker-compose-test.yml down

# but we specified a project name, so we need to include that too:
docker-compose -f ./numbers/docker-compose.yml -f ./numbers/docker-compose-test.yml -p numbers-test down

注入自定义参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
services:
  todo-web:
    image: diamol/ch06-todo-list
    ports:
      - "${TODO_WEB_PORT}:80"
    environment:
      - Database:Provider=Sqlite
    env_file:
      - ./config/logging.debug.env
    secrets:
      - source: todo-db-connection
        target: /app/config/secrets.json

secrets:
  todo-db-connection:
    file: ./config/empty.json
  • secrets 参数注入

    docker-compose 将会将 source./config/empty.json 拷贝到容器的 target/app/config/secrets.json

  • 环境变量注入方式,可以用 environment 直接在 yml 中配置,也可以 env_file 指定参数配置文件,文件中每一行就是一个 KEY=VALUE 的参数配置。

  • host 环境变量

    例如,上例中${TODO_WEB_PORT} , docker-compose 可以从同目录的 .env 文件中读取,当然也可以从系统变量中读取。

    .env 文件的例子:

    1
    2
    3
    4
    5
    6
    7
    8
    # container configuration - ports to publish:
    TODO_WEB_PORT=8877
    TODO_DB_PORT=5432
      
    # compose configuration - files and project name:
    COMPOSE_PATH_SEPARATOR=;
    COMPOSE_FILE=docker-compose.yml;docker-compose-test.yml
    COMPOSE_PROJECT_NAME=todo_ch10
    

yml配置文件,预定义属性减少重复和冗余

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
-labels: &logging
  logging:  
    options:
      max-size: '100m'
      max-file: '10'

x-labels: &labels
  app-name: image-gallery

services:
  accesslog:
    <<: *logging
    labels:
      <<: *labels

  iotd:
    ports:
      - 8080:80
    <<: *logging
    labels:
      <<: *labels
      public: api

性能监测

Prometheus

An open source project to do containerized monitoring.
It runs in a Docker container, and collects metrics data from the applications and the Docker Engines.
It stores all data with the timestamp when it was collected.

需要手动在Docker Engine 的设置中开启 Prometheus metrics:

1
2
"metrics-addr" : "0.0.0.0:9323",
"experimental": true

以上设置,开启 监控功能,设置metrics数据的获取端口在 9323。

访问 http:/ /localhost:9323/metrics 查看当前监控信息,都是key-value列表,不是图表,看起来挺乱,这就是 Prometheus 的原始数据格式。

启动 Prometheus 的时候,告知 host 的 IP。

1
2
3
4
5
6
7
8
9
10
# load your machine's IP address into a variable - on Windows:
$hostIP = $(Get-NetIPConfiguration | Where-Object {$_.IPv4DefaultGateway -ne $null }).IPv4Address.IPAddress

# on Linux:
hostIP=$(ip route get 1 | awk '{print $NF;exit}')
# and on Mac:
hostIP=$(ifconfig en0 | grep -e 'inet\s' | awk '{print $2}')

# pass your IP address as an environment variable for the container:
docker container run -e DOCKER_HOST=$hostIP -d -p 9090:9090 diamol/prometheus:2.13.1

访问 http://localhost:9090 就可以看到 Prometheus 的 Web UI 界面了。

集群

  • Docker Swarm
  • Kubernetes

技巧

docker 清除容器和镜像

1
2
3
docker container rm -f $(docker container ls -aq)

docker image rm -f $(docker image ls -f reference='diamol/*' -q)

docker 使用的磁盘空间

1
docker system df