Docker

Docker 准备工作

Docker Playground

Docker Playground

这是一个不用下载的,在线就可以用的Docker。使用需要使用Docker的账号密码,可以去注册一个。

下载 Docker (已弃用)

Docker分为社区版和企业版。这里主要介绍社区版。

进入官方DockerToolbox下载页面,按照安装教程安装Docker Toolbox。

国内阿里云[DockerToolBox下载页面]
(http://mirrors.aliyun.com/docker-toolbox/windows/docker-toolbox/DockerToolbox-18.03.0-ce.exe),如果官方的速度太慢,可以切换到这里。这里的Docker不是最新版的。

其他下载页面,包含Ubuntu下的一键安装脚本。

安装过程会自动安装VirtualBox,Dokcer,Docker-compose以及Kitematic。

Docker 容器查询
Docker 官方文档
W3C Docker 参考文档

下载 Docker

Windows 10及以上版本可以到官网下载。安装前需要先安装相关组件,在控制面板->程序->程序和功能->启用和关闭Windows功能中安装适用于Linux的Windows子系统,安装完成后再安装WSL2升级包。安装完成后再安装Docker即可。

Linux系统安装过程更为简单,只需要执行下面的命令即可。

1
curl -sSL https://get.docker.com | sh

Docker Compose安装:到Github上下载二进制包,添加到环境变量中即可。

配置 Docker

使用Docker,Docker会默认为我们创建一个虚拟机,下载的Image也都会存储在虚拟机中。默认情况下,虚拟机会存储在用户文档下的.docker目录下。若想改变虚拟机默认存储,可以配置环境变量MACHINE_STORAGE_PATH即可。

(如果遇到Waiting for an IP无限等待,可能是OpenSSH的问题,这个是Win10上内置的功能)

运行桌面上的Docker Quickstart Terminal,首次启动Docker会为我们创建一台虚拟机,并在这个虚拟机目录下创建配置文件。打开配置文件(如果修改过就去改后的目录找):

1
C:\Users\用户名\.docker\machine\machines\default\config.json

HostOptions->EngineOptions->RegistryMirror中配置为:

1
2
3
{
"RegistryMirror": ["https://sfpj1t4c.mirror.aliyuncs.com"],
}

新版本为:上方的设置按钮,Docker Engine中配置。

此处的镜像地址最好是到阿里云找一个。登录阿里云,点击产品与服务,选择容器镜像服务->镜像中心->镜像加速器。

配置完成后,重新运行桌面上的Docker Quickstart Terminal

首次使用 Docker

首先进入Docker Quickstart Terminal后,在项目目录执行如下内容,用来测试docker是否正常。

1
docker run ubuntu:16.04 /bin/echo "Hello world"

提示Hello World说明配置成功。

Docker 安装了什么

安装完成后,我们来简单梳理一下都做了什么。

Docker在我们的电脑上安装了:

  • VirtualBox:虚拟机工具。
  • Docker Machine:虚拟机管理工具。
  • Docker Compose:Docker脚本执行工具。
  • Docker Client

完成安装后,首次运行Docker Quickstart Terminal,Docker就会创建一个虚拟机,作为我们的Docker Server,而我们的本地系统则成为了Docker Client。

之后我们使用的容器将全部运行在这个虚拟机中。查看虚拟机的IP可以使用命令:

1
docker-machine env

Ubuntu 下的 Docker

如果图个方便,那么直接安装:

1
sudo apt-get install -y docker.io containerd runc docker-compose

如果想去官方下载最新版,那么可以按照官方的程序来一遍。

Docker for Linux安装方法

按照官网给出的安装方法安装Docker。此外,官网还给出了下载Docker包安装和卸载Docker的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 首先删除旧版本
sudo apt-get remove docker docker-engine docker.io containerd runc

# 更新下载源
sudo apt-get update

# 配置apt
sudo apt-get install -y \
apt-transport-https \
ca-certificates \
curl \
gnupg-agent \
software-properties-common

# 添加Docker源秘钥
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

# 添加秘钥
sudo apt-key fingerprint 0EBFCD88

# 添加Docker源
sudo add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) \
stable"

sudo apt-get update

# 安装 Docker Community
sudo apt-get install -y docker-ce docker-ce-cli containerd.io

# 如果想要安装其他版本,可以尝试下面的操作
# 列出可以按照的Docker Community版本
apt-cache madison docker-ce

# 选择一个版本安装Docker Community,注意替换下面<...>内容
sudo apt-get install -y docker-ce=<VERSION_STRING> docker-ce-cli=<VERSION_STRING> containerd.io

# 安装完成,测试安装结果
sudo docker run hello-world

修改Docker配置文件:

在/etc/docker/daemon.json中增加如下内容:

1
2
3
{
"registry-mirrors": ["https://sfpj1t4c.mirror.aliyuncs.com"]
}

此处的镜像地址最好是到阿里云找一个。登录阿里云,点击产品与服务,选择容器镜像服务->镜像中心->镜像加速器。

Ubuntu 快速安装指南

1
2
3
4
5
6
7
8
9
10
11
12
curl -sSL https://get.docker.com | sh  # 一键安装
sudo cat>>/etc/docker/daemon.json<<EOF
{
"registry-mirrors": ["https://sfpj1t4c.mirror.aliyuncs.com"]
}
EOF
sudo systemctl daemon-reload # 加载阿里云加速镜像
sudo systemctl restart docker
sudo groupadd docker # 添加当前用户到docker组
sudo usermod -aG docker $USER
newgrp docker
sudo apt-get install -y docker-compose # 安装 docker compose

配置VS Code远程开发,修改/lib/systemd/system/docker.service文件

1
ExecStart=/usr/bin/dockerd -H unix:///var/run/docker.sock -H tcp://0.0.0.0:2375

再重新加载Docker

1
2
sudo systemctl daemon-reload  # 加载阿里云加速镜像
sudo systemctl restart docker

VS Code安装docker插件,打开配置,输入docker:host,编辑host

下载Docker Cli,加入到环境变量中。

任务管理器->服务->右键->打开服务中启动SSH Agent服务(OpenSSH Authentication Agent)。

1
get-service ssh*  # 查看服务状态

在本地主机上配置SSH,生成公钥id_rsa.pub和私钥id_rsa

1
ssh-keygen

公钥放到远程主机上,追加到~/.ssh/authorized_keys文件中。
本地执行命令,添加私钥。

1
ssh-add id_rsa

配置本地Docker的Context

1
2
3
docker context create context_name --docker "host=ssh://user@host"
docker context use context_name
docker info # 测试结果

GPU 支持

NVIDIA Docker

如果要安装Docker的GPU支持,可以下载该模块。实现GPU支持后,可以使用Tensorflow-GPU的Docker。

Docker 原理与架构

Docker 是一个平台,它通过Docker Engine把底层的设备与上层应用隔离开。Docker Engine是一个后台进程,即是一个REST API Server,它还有一个CLI接口(docker)。

Docker 包含 Client 与 Server 。Docker Client 通过命令方式控制 Docker Server,在Server上操作Container,Image等。Docker Server通过代码仓库或Docker Hub获取Docker镜像。

Docker Image

Image 本身是只读的。Docker搭建的应用的过程就是将几个Image一层一层叠加上去的过程。例如在Ubuntu Image层上叠加Apache Image层,在上面再叠加一个PHP Image层,就可以实现一个网站。Image之间可以共享同一层,例如Apache与MySQL可以在同一层上。

使用 Image

1
2
3
4
5
6
7
8
9
10
11
12
# 查看 image
docker image ls

# 删除 image
docker image rm image_id

# 获取 image
docker pull ubuntu:14.04

# 通过 Dockerfile 创建 image -t 表示 Tag . 表示当前目录
docker build -t author/name:version .

如果要查询Image,可以去Docker Hub上查找。

创建 Image

如果想要自己创建一个Base Image,可以这样做。

例如我们编写一个 c 程序。

1
vim main.c

编辑程序代码:

1
2
3
4
5
6
#include <stido.h>

int main(int argc, char* argv[])
{
printf("hello\n");
}

编译程序:

1
gcc -static main.c -o main

执行程序查看效果:

1
./main

编写Dockerfile文件:

1
vim Dockerfile
1
2
3
4
# scratch 表示没有Base
FROM scratch
ADD main /
CMD ["/main"]

构建运行Image:

1
2
3
4
5
6
# 构建
docker build -t author/image_tag .
# 查看Image层数
docker history image_id
# 运行
docker run author/image_tag

删除 Image

删除Image:

1
2
docker image rm image_id
docker rmi image_id

Docker Container

Container 是一种可读可写的层。我们在运行一个Image时,Docker会在这个Image上添加Container层,用于读写程序运行时产生的数据。

我们也可以把Container与Image类比成对象和类。Image只读,负责存储app,可以当做是一个类;Container则是负责运行app,可以当做是一个对象。

使用 Container

运行一个Image,就创建了一个Container。Container在运行完成后就会退出。

1
docker run ubuntu

运行完成后:

1
2
3
4
5
6
# 查看所有容器,-a 表示包括已经结束运行的
docker container ls -a
# 或
docker ps -a
# 只显示container id
docker container ls -aq

使用如下命令可以进入容器内做交互,注意,这里的所有数据会在Container结束运行时消失:

1
2
# -i 表示交互  -t 表示标准输入输出
docker run -it ubuntu

删除Container:

1
2
3
4
5
6
7
docker container rm container_id
# 或
docker rm container_id
# 批量删除所有container
docker rm $(docker container ls -aq)
# 批量删除所有退出的container
docker rm $(docker container ls -f "status=exited" -q)

当我们在container中产了数据,做了某些操作,我们就可以通过commit方式将修改后的contianer变为image。

从Container创建Image

1
2
3
4
5
docker container commit # 同docker commit


# 例如
docker commit contaienr_name image_name

我们也可以从Dockerfile创建container。首先创建一个空的目录,这个目录下的除配置文件外的所有文件将会被打包进入image。我们创建一个配置文件Dockerfile:

1
2
FROM ubuntu
RUN sudo apt-get install -y vim

执行命令打包:

1
2
3
4
docker container build  # 同 docker build

# 例如
docker build -t image_name .

进入运行中的container:

1
2
3
4
# 进入命令行
docker exec -it container_id /bin/bash
# 显示ip
docker exec -it container_id ip a

停止container:

1
docker stop container_id

运行时给container命名:

1
docker run -d --name=demo image_name

这样就可以不用再操作container_id了,而是可以直接操作name。

查看container信息:

1
docker inspect container_id

查看container输出信息:

1
docker logs container_id

上传自制 Image

首先注册登录Docker,并进入Docker Hub。

在本地命令行:

1
2
3
4
5
# 登录账号
docker login
# 推送 image
docker push author/image:latest
# 被推送的image必须是 author/image:latest 格式,否则没有权限推送

进入Docker Hub就可以看见自己推送的Image了。

也可以让Docker与Github关联。在Docker页面,Create,Create Automated Build里面,选择Link Accounts关联Github。在Github里创建Repository,将Dockerfile上传到代码仓库。Docker的服务器会帮我们Build镜像。

如果要搭建自己的Image仓库,可以在Docker hub里面搜索registry,按照里面的教程操作即可。

打包一个Python程序到Image

创建一个Python脚本

首先创建Docker打包目录,在目录下创建文件app.py

1
2
3
4
5
6
7
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return "hello docker"
if __name__ == '__main__':
app.run()

创建一个 Dockerfile

再创建Dockerfile:

1
2
3
4
5
6
7
FROM python:2.7
LABEL maintainer="Author"
RUN pip install flask
WORKDIR /app
COPY app.py /app/
EXPOSE 5000
CMD ["python", "app.py"]

创建 Image

1
2
3
docker build -t author/image_name .
# -d 参数后台执行
docker run -d author/image_name

如果创建失败,遇到bug想要调试,可以查看build日志,找到最后一个创建成功的Step,复制其id,并通过命令进入bash环境:

1
docker run -it id /bin/bash 

Docker 压力测试

进入docker容器中:

1
docker run -it ubuntu

安装stress工具:

1
sudo apt-get update && sudo apt-get install -y stress

使用stress:

1
2
3
4
5
6
# vm Worker数 
# verbose 打印日志
# 功能:反复分配释放内存,默认256MB
stress --vm 1 --verbose

# vm-bytes 申请释放内存大小

或使用Dockerfile

1
2
3
4
FROM ubuntu
RUN apt-get update && apt-get install -y stress
ENTRYPOINT ["/usr/bin/stress"]
CMD [""] # 从命令行接受参数
1
2
docker build -t image_tag .
docker run -it image_tag --vm 1 --verbose

创建一个常开的Container

使用busybox这个Image,可以创建一个常开的Container:

1
docker run -d --name test busybox /bin/sh -c "while true; do sleep 3600; done"

限制Container资源

在开启Container时,可以通过添加参数限制Container的资源,包括cpu个数,内存大小等:

1
2
3
4
# --memory 内存 --cpu-shares cpu相对占用
docker run --memory=200M --cpu-shares=10 image_tag --vm 1 --cpu 1 --verbose
docker run --memory=200M --cpu-shares=5 image_tag --vm 1 --cpu 1 --verbose
# 想当与2:1占用一个cpu

Docker Network

单机Network有三种模式:

  • Bridge Network
  • Host Network
  • None Netw

多机Network有 Overlay Network。

手工配置 Linux Network 命名空间

Network命名空间(Namespace)是一种虚拟化技术,它可以将一个物理机虚拟化成多个虚拟机。一个命名空间相当于一个虚拟主机。我们可以通过配置命名空间下的虚拟端口,可以完成虚拟机,也就是命名空间中的网络连接。这也是docker容器的底层技术。

Linux的Network命名空间有关命令:

1
2
3
4
5
6
7
8
9
# 查看所有命名空间
sudo ip netns list
# 添加命名空间
sudo ip netns add net_test1
# 删除命名空间
sudo ip netns delete net_test1
# 查看命名空间下的IP
sudo ip netns exec net_test1 ip a
# 会看到该命名空间下的IP没有任何启动的虚拟网卡

配置虚拟网络的过程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# veth即为端口,首先创建一对连起来的端口
sudo ip link add veth_test1 type veth peer name veth_test2
# 将端口添加到命名空间中。
sudo ip link set veth_test1 netns net_test1
sudo ip link wet veth_test2 netns net_test2
# 为端口分配IP地址
sudo ip nets exec net_test1 ip addr add 192.168.1.1/24 dev veth_test1
sudo ip nets exec net_test2 ip addr add 192.168.1.2/24 dev veth_test2
# 启动命名空间网络
sudo ip netns exec net_test1 ip link set dev veth_test1 up
sudo ip netns exec net_test2 ip link set dev veth_test2 up
# 查看结果
sudo ip netns exec net_test1 ip link
sudo ip netns exec net_test2 ip link
# 会看到网络已经启用了,两个Network命名空间也连起来了

Bridge Network

Bridge Network 原理探索

查看Linux本机IP:

1
ifconfig

可以看到docker0网桥,veth453e607端口以及其他的网络设备。其中:

  • docker0:是一个网桥,是Docker服务端上用于连接其他设备的端口。
  • veth…:是Docker Container上的端口。它是成对出现的,而它的另一端连接到docker0上。

可以使用下面的工具查看这个拓扑结构。

1
2
sudo apt-get install -y bridge-utils
brctl show

Bridge Network 使用

查看所有的Docker网络:

1
docker network ls

我们这里创建两个容器,并让第二个通过桥接方式连接到第一个容器上。

1
2
3
4
docker run -d --name test1 busybox /bin/sh -c "while true; do sleep 3600; done"
# 使用 --link 连接到另一个容器
docker run -d --name test2 --link test1 busybox /bin/sh -c "while true; do sleep 3600; done"
# link 命令并不常用

这样test2就可以直接通过hostname访问test1,但是test1无法通过hostname访问test1。但是二者可以通过IP访问。

另外,我们也可以通过network方式连接两个容器。创建好的容器默认连接到bridge上。我们新建一个bridge,并让两个容器联入新的bridge。

1
2
3
4
5
6
7
8
9
10
# 创建网桥
docker network create -d bridge test_bridge
# 查看网桥
docker network ls
# 新建容器并联入网桥
docker run -d --name test3 --network test_bridge busybox /bin/sh -c "while true; do sleep 3600; done"
# 对已经有的容器联入网桥
docker network connect test_bridge test2
# 查看连接状态
docker network inspect netword_id

之后,我们还要将端口映射出来。

新建一个Nginx服务器用于测试。

1
2
# 将本地8030端口映射到容器的80端口
docker run --name web -d -p 8030:80 nginx

访问本地8030端口即可查看。

Host Network

Host网络是与主机共享一个Network命名空间。启动一个连接Host网络的容器:

1
docker run -d --name test4 --network host busybox /bin/sh -c "while true; do sleep 3600; done"

这样的容器将直接使用主机上的端口工作。

None Network

None网络是一个孤立网络。启动一个连接None网络的容器:

1
docker run -d --name test6 --network none busybox /bin/sh -c "while true; do sleep 3600; done"

这个容器将不接入任何网络。

Overlay Network

通过Overlay Network可以实现不同物理机上的Docker容器通信,Docker通过VXLAN技术实现了Docker容器在不同物理机上的通信。这里可以使用etcd实现分布式存储,用于辅助Overlay网络。

使用方法:

1
docker network create -d overlay network_name 

这样不论在哪个物理机上操作Docker,都操作的是同一个服务,也就是两台物理机上使用的同一个Docker。

Docker 数据持久化

Data Volume

Volume有两种,一种是作为本地文件存储的Volume,另一种是通过第三方插件,如NAS,AWS等。

创建的Volume有两种,一种是作为Docker对象呈现,可以用命令查看:

1
docker volume ls

另一种是直接挂在到本地目录。

对于前者,首先使用Dockerfile定义Volume:

1
VOLUME ["/var/lib/mysql"]

创建的时候再指定参数:

1
docker run -d --name mysql_test -v mysql:/var/lib/mysql -e MYSQL_ALLOW_EMPTY_PASSWORD=true mysql

Bind Mouting

这种模式不必创建Volume,而是可以直接使用,也就是将目录挂载到本地,实现目录的同步:

1
2
3
docker run -v /home/aaa:/root/aaa
# 例如
docker run -d 80:80 -v $(pwd):/usr/share/nginx/html --name web nginx

Docker Compose

Docker Compose可以通过脚本快速搭建容器集群,适用于开发环境。

一个例子

1
2
3
4
5
6
7
8
docker run -d --name mysql-test -v mysql-data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=root \
-e MYSQL_DATABASE=wordpress \
mysql

docker run -d --name wordpress-test \
-e WORDPRESS_DB_HOST=mysql-test:3306 \
--link mysql -p 8080:80 wordpress

常用命令

1
2
3
4
5
6
7
8
9
10
11
12


rm
kill
down

up
start
restart
logs
exec
stop

简介

Docker Compose相当于一个批处理工具,可以定义,管理多个docker应用。

Docker Compse有三大概念:

  • Services:代表一个容器,可以指定Network和Volume。
  • Network:
  • Volumes:

使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
version: "3"

services:
db:
image: postgres:9.4
volumes:
- db-data:/var/lib/postgresql/data
networks:
- back-tier

worker:
build: ./worker
ports:
- 8080:80
environment:
ENV_A: value_a
links:
- db
- redis
# 不用links,可以用networks
networks:
- back-tier

worker_2:
build:
context: .
dockerfile: Dockerfile
ports:
- 8000:5000
environment:
ENV_B: value_b

volumes:
db-data:

networks:
back-tier:
driver: bridge
front-tier:
driver: bridge
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 启动一个脚本
docker-compose -f docker-compose.yml up
# 如果脚本文件名就是docker-compose.yml,可以简写:
docker-compose up
# 后台执行
docker-compose up -d
# 查看服务
docker-compose ps
# 停止服务
docker-compose stop
# 启动服务
docker-compose start
# 停止并删除服务
docker-compose down
# 进入服务
docker-compose exec name bash
# 显示运行的进程
top
# 打印日志 -f 跟踪日志
logs
# 运行一次性命令,默认不会绑定服务器端口
run
run web bash
run web env
# 编译
build

弹性伸缩

当我们需要做负载均衡时,可以使用伸缩的功能创建多个Web服务器,并用redis来存储客户的sessions。我们可以设计如下的架构:

负载均衡器 $
\begin{Bmatrix}
Web服务 1 \
Web服务 2 \
Web服务 3 \
\end{Bmatrix}
$Redis数据库

命令行方式创建多个服务:

1
docker-compose up --scale service_name=3 -d 

使用脚本方式创建负载均衡器,多个Web服务,以及Redis数据库:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
version: "3"

services:
redis:
image: redis

web:
build:
context: .
dockerfile: Dockerfile
environment:
REDIS_HOST: redis

lb:
image: dockercloud/haproxy
links:
- web
port:
- 8080:80
volumes:
- /var/run/docker.sock:/var/run/docker.sock

启用脚本:

1
docker-compose up

将web服务增加到3个:

1
docker-compose up --scale web=3 -d 

环境变量

环境变量:在同一目录里下建立.env文件,用于设置环境变量。在docker-compose.yml中启用环境变量如下:

1
2
3
4
5
6
7
8
9
10
services:
web:
environment:
# 本地赋值
- REDIS_HOST=redis
# 引用外部环境变量
- DEBUG
env_file:
# 使用环境变量文件
- web-variables.env

查看一个服务使用了哪些环境变量

1
docker-compose run web env

内置环境变量

1
2
3
COMPOSE_PROJECT_NAME
COMPOSE_FILE
DOCKER_HOST - defaults to unix:///var/run/docker.sock,容器内的程序通过此套接字与Docker守护进程通信

预设与多模块

如果想要给docker-compose.yml配置不同环境下,启动不同的容器的设置,可以使用profile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
version: "3.9"
services:
frontend:
image: frontend
profiles: ["frontend"]

phpmyadmin:
image: phpmyadmin
depends_on:
- db
profiles:
- debug

backend:
image: backend

db:
image: mysql

当启动容器时,指定profile,就可以开启特定的几个服务。

1
docker-compose --profile frontend --profile debug up

默认情况下,Compose会读取两个文件:docker-compose.ymldocker-compose.override.yml,同时合并两个文件的结果。如果有其他文件,可以用-f来合并每个文件。

1
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d

网络

每一个Compose对应一个网络。同一个Compose下的容器可以通过容器名互相访问。

也可以使用Links为被连接的容器起别名。

1
2
3
4
5
6
7
8
9
version: "3.9"
services:

web:
build: .
links:
- "db:database"
db:
image: postgres

也可以使用多网络,更改网络驱动,连接外部网络等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
version: "3.9"

services:
proxy:
build: ./proxy
networks:
- frontend
app:
build: ./app
networks:
- frontend
- backend
db:
image: postgres
networks:
- backend

networks:
frontend:
# Use a custom driver
driver: custom-driver-1
backend:
# Use a custom driver
driver: custom-driver-2

# 或配置默认网络
networks:
default:
# Use a custom driver
driver: custom-driver-1

networks:
default:
external: true
name: my-pre-existing-network

控制启动顺序

启动顺序由depends_onlinksvolumes_fromnetwork_mode: "service:..."决定。

depends_on:当依赖容器启动后再启动自己。

Docker Swarm

Docker Swarm可以快速搭建容器集群,适用于生产环境。在生产环境中,为了保障服务的正常使用,我们决不能停止当前的服务。不仅如此,我们还需要实时监控服务的状态,甚至在宕机时能够自动恢复。另外,服务还要有足够的安全性,防止数据泄露,扛得住网络攻击。

Swarm 架构

在Swarm中有两种角色。一种是Worker,一种是Manager。Manager是管理Worker的节点,可以有多个,且他们之间数据可以同步,同步使用Raft,。Worker是处理数据的节点,也可以有多个,他们之间通过Gossip network通信。

在Swarm中还定义了Service与Replicas。Service代表了一种服务,而Replicas则是服务下属的节点,一个服务可以有多个下属的节点。每一个Replica是一个容器。

创建集群

首先创建三台虚拟机,命名为Manager,Worker1,Worker2。

1
2
3
4
5
6
7
8
# 创建虚拟机
docker-machine create swarm-manager
docker-machine create swarm-worker1
docker-machine create swarm-worker2
# 查看虚拟机IP,例如得到ManagerIP为192.168.205.10
docker-machine ls
# 登录Manager
docker-machine ssh swarm-manager

在Manager节点上执行:

1
docker swarm init --advertise-addr=192.168.205.10

之后会返回一条指令,这条指令是给要加入Manager的Worker节点使用的:

1
docker swarm join --token SWMTKN-1-3cv6sadfwe...asfwef 192.168.205.10:2377

进入Worker1节点,执行刚刚得到的指令,即可加入集群。

在Manager节点可以查看Worker情况:

1
docker node ls

横向扩展

横向扩展是指通过创建多个服务来做负载均衡,以保证服务的可靠性。依然是上面的三个虚拟机的集群,在Manager上搭建服务:

1
docker service create --name demo busyBox sh -c "while true; do sleep 3600; done"

查看刚刚搭建的服务:

1
docker service ps demo

可以看到一个REPLICAS属性,这个属性就是扩展的节点数。下面我们扩展这个服务:

1
docker service scale demo=5

再次查看操作结果:

1
docker service ps demo

会发现5个服务被平均分配到3台虚拟机上了。如果某个服务宕机了,Swarm会自动新建一个节点,修复集群。

停止服务:

1
docker service rm demo

在线更新

为了保证更新过程,服务不宕机,Swarm提供了动态更新的功能。

首先创建一个overlay网络:

1
docker network create -d overlay demo

在这个网络上创建一个服务,这个服务是一个旧版的Nginx,一会我们通过动态更新换为最新版Nginx。更新之前,首先保证服务节点数大于一个:

1
2
docker service create --name web --publish 8080:5000 --network demo nginx:1.16.1
docker service scale web=4

更新Image:

1
docker service update --image nginx:latest web

更新端口:

1
docker service update --publish-rm 8080:5000 --publish-add 8088:5000 web

在更新过程中,会有旧版和新版同时服务的状况。

搭建WordPress

使用WordPress,需要有WordPress和MySQL两个服务。

首先在Manager节点,创建Overlay网络:

1
docker network create -d overlay demo

创建服务:

1
2
3
4
5
6
7
8
9
10
11
12
docker service create --name mysql \
--env MYSQL_ROOT_PASSWORD=root \
--env MYSQL_DATABASE=wordpress \
--network demo \
--mount type=volume,source=mysql-data,destination=/var/lib/mysql \
mysql

docker service create --name wordpress -p 80:80 \
--env WORDPRESS_DB_PASSWORD=root \
--env WORDPRESS_DB_HOST=mysql \
--network demo \
wordpress

之后三个节点的IP地址都可以访问WordPress服务了。

DNS 服务

在Docker内部拥有一个DNS服务,这个服务维护了每一个服务以及其服务IP。如果这个服务有3个节点,那么这个服务就会另外有3个IP,对应3个容器的IP。也就是说,这个服务一共有4个IP:1个虚拟的IP用于对外提供服务,这个IP会保持不变;另外还有3个用于对应容器,可以随时扩展收缩。

我们可以通过访问虚拟机集群,不论访问哪个虚拟机的IP,都能访问容器中的服务。在这个过程中,Docker DNS会自动为我们寻找需要的服务和它的虚拟IP。但是我们请求这个服务,最终会看到容器所在的虚拟机的主机名。

对比Docker Overlay网络,Overlay实现了多个虚拟机之间的容器通信,使用的技术是VXLAN Tunnel;而服务之间则是通过虚拟IP,基于Docker DNS通信,使用LVS(Linux Virtual Server)技术。

使用 Stack 通过 Docker Compose 部署服务

使用Stack通过Docker Compose更新服务,首先要确保image不能是在本脚本build,而是必须使用已经build好的image。

deploy属性指定了更新的策略,包括部署数量,部署方式等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
version: "3"

services:
redis:
image: redis:alpine
ports:
- "6379"
networks:
- frontend
deploy:
replicas: 2
update_config:
parallelism: 2
delay: 10s
restart_policy:
condition: on-failure

worker:
image: web
networks:
- frontend
- backend
deploy:
mode: replicated
replicas: 1
labels: [APP=VOTING]
restart_policy:
condition: on-failure
delay: 10s
max_attempts: 3
window: 120s
placement:
constraints: [node.role == manager]

db:
image: postgres:9.4
volumes:
- db-data:/var/lib/postgresql/data
networks:
- backend
deploy:
placement:
constraints: [node.role == manager]

networks:
frontend:
backend:

volumes:
db-data:

使用docker compose脚本搭建的集群,需要脚本中包含deploy属性,并使用docker stack使用与更新服务:

1
2
3
4
5
6
7
8
# 部署服务
docker stack deploy example --compose-file=docker-compose.yml

# 查看服务
docker stack ls

# 查看服务情况
docker stack services example

最后删除服务:

1
docker stack rm example

另外推荐一个服务visualizer,可以可视化查看集群中每个虚拟机上的容器状况。

如果更新服务,可以直接修改docker compose文件,然后重新部署:

1
docker stack deploy example --compose-file=docker-compose.yml

Secret 管理

在生产环境中,用于管理系统的密码一般不会明文写到docker compose中,而是使用一些手段隐藏起来。

因此用到Secret管理。这里的Secret管理主要包括:

  • 用户名/密码
  • SSH Key
  • TLS 认证
  • 机密数据

在Manager节点中,有一个内置的分布式存储。在这个存储中我们就可以存储我们的Secret。当Worker上有容器想要使用时,就可以请求分布式存储获取Secret。

首先创建一个文件password.txt,将我们的密码写到这个文件中。创建一个Secret:

1
docker secret create demo-password password.txt

之后删掉password.txt

查看创建的Secret:

1
docker secret ls

不创建文件而直接创建Secret也可以,通过管道的方式即可:

1
echo "admin123456" | docker secret create demo-password2

删除Secret可以:

1
docker secret rm demo-password2

使用Secret:

1
docker service create --name client --secret demo-secret busybox sh -c "while true;do sleep 3600;done"

在容器的/run/secrets/下可以看到刚刚的secret,是可以看到密码原文的。在MySQL中,可以使用MYSQL_ROOT_PASSWORD_FILE指定密码文件。

在compose中也可以使用Secret。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
version: "3"

services:
image: mysql
secrets:
- demo-password
environment:
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/demo-password
volumes:
- mysql-data:/var/lib/mysql

volumes:
mysql-data:

# 不推荐下面的创建方法,而是推荐命令行管道方式创建Secrets
secrets:
demo-password:
file: ./password

Docker 开发

系统镜像

BusyBox

包含常用命令,体积小于2MB。

1
2
docker pull busybox
docker run -it busybox

Alpine

面向安全的轻型Linux发行版,大小仅5MB左右,还包括了APK包管理器。

1
docker run alpine /bin/bash

包管理器使用:

1
2
3
apk add --no-cache <package>
echo "http://dl-4.alpinelinux.org/alpine/dege/testing" >> /etc/apk/repositories
apk --update add --no-cache <package>

包名可以到该网站查阅。

Debian / Ubuntu

非常适合研发场景,支持多种计算机系统结构。

1
docker run -it debian bash

另外还有 CentOS Fedora 等。

添加SSH服务

使用Commit创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
docker run -it ubuntu
# 进入容器
apt-get update
vi /etc/apt/sources.list.d/163.list
# 添加 163 镜像源
apt-get update
# 安装 ssh 服务
apt-get install openssh-server
mkdir -p /var/run/sshd
/usr/sbin/sshd -D &
# 查看端口
netstat -tunlp
# 取消pam登录限制
sed -ri 's/session required pam_loginuid.so/#session required pam_loginuid.so/g' /etc/pam.d/sshd
# 复制主机上的公钥到容器 ssh-keygen -t rsa
mkdir root/.ssh
vi /root/.ssh/authorized_keys
# 创建SSH启动脚本
vi /run.sh
chmod +x run.sh
# 内容如下
#!/bin/bash
/usr/sbin/sshd -D
# 退出容器
exit
# 提交容器 作为新镜像
docker commit container_id sshd:ubuntu

使用Dockerfile创建

1
2
3
4
5
6
7
8
9
10
11
# 创建工作目录
mkdir sshd_ubuntu
cd sshd_ubuntu
# 创建Dockerfile
touch Dockerfile run.sh
# Dockerfile
#!/bin/bash
/usr/sbin/sshd -D
# 复制公钥过来
cat ~/.ssh/id_rsa.pub > authorized_keys
# 编写Dockerfile

Web 服务

Apache

官方Apache不带PHP,如果需要,可以到该网站查询: 7.0.7-apache

编写Dockerfile

1
2
3
FROM httpd:2.4
COPY ./public-html /usr/local/apache2/htdocs/

编译镜像

1
docker build -t apache2-image .

基于 Ubuntu Debian 的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
# 时区
RUN echo "Asia/Shanghai" > /etc/timezone
# 相关环境变量
ENV APACHE_RUN_USER www-data
ENV APACHE_RUN_GROUP www-data
ENV APACHE_LOG_DIR /var/log/apache2
ENV APACHE_PID_FILE /var/run/apache2.pid
ENV APACHE_RUN_DIR /var/run/apache2
ENV APACHE_LOCK_DIR /var/lock/apache2
ENV APACHE_SERVERADMIN admin@localhost
ENV APACHE_SERVERNAME localhost
ENV APACHE_SERVERALIAS docker.localhost
ENV APACHE_DOCUMENTROOT /var/www

Nginx

使用 Docker-compose

1
2
3
4
5
web:
image: nginx
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
command: [nginx-debug, '-g', 'daemon off']

针对Nginx服务器的内核优化命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
kernel.sysrq = 0
kernel.core_uses_pid = 1
kernel.ms-nb = 65536
kernel.ms-ax = 65536
kernel.shmmax = 68719476736
kernel.shmall = 4294967296
net.ipv4.ip_forward = 0
net.ipv4.conf.default.rp_filter = 1
net.ipv4.conf.default.accept_source_route = 0
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_tx_buckets = 6000
net.ipv4.tcp_sack = 1
net.ipv4.tcp_window_scaling = 1
net.ipv4.tcp_rmem = 4096 87380 41494304
net.ipv4.tcp_wmem = 4096 16384 41494304
net.ipv4.tcp_max_orphans =
net.ipv4.tcp_max_syn_backlog =
net.ipv4.tcp_timestamps =
net.ipv4.tcp_synack_retries =
net.ipv4.tcp_syn_retries =
net.ipv4.tcp_tw_recycle =
net.ipv4.tcp_tw_reuse =
net.ipv4.tcp_mem =
net.ipv4.tcp_fin_timeout =
net.ipv4.tcp_keepalive_time =
net.ipv4.ip_local_port_range =
net.core.rmem_default =
net.core.wmem_default =
net.core.rmem_max =
net.core.wmem_max =
net.core.netdev_max_backlog =
net.core.somaxconn =

Tomcat

实现 Servlet 和 JSP 支持。

Jetty

实现 Servlet 支持。

LAMP

  • linode/lamp
  • tutum/lamp

持续开发与管理

CI : 持续集成;CD : 持续交付

Jenkins

1
docker run -p "8000:8080" -p "50000:50000" -v ~/home:/var/jenkins_home jenkins

GitLab

1
docker run ...

数据库

MySQL

应用广泛。

查看日志

1
docker logs mysql_name

自定义配置文件

1
2
3
4
5
# 编辑此配置目录 /etc/mysql/conf.d

# MySQL 将结合下面的两个配置启动
# /etc/mysql/my.conf
# /etc/mysql/conf.d/config-file.cnf

脱离cnf文件进行配置

1
2
# 通过标签flags传递配置选项
docker run ... mysql:tag --character-set-server=utf8mb4

Oracle Database XE

免费,体机小,配置简单。适用于PHP,JAVA,.NET等。

MongoDB

一种NoSQL,并提供关系型数据库的绝大部分功能。

Redis

基于内存的数据结构存储系统,用作缓存和消息中间件。

Cassandra

开源分布式数据库,支持Spark,Storm,Hadoop系统集成,类似与HBase。

分布式处理与大数据平台

常见工具:

  • Hadoop
  • Spark
  • Storm
  • Elasticsearch

Hadoop

sequenceiq/hadoop-docker:2.7.0

1
docker run -it sequenceiq/hadoop-docker:2.7.0 /etc/bootstrap.sh -bash

Spark

sequenceiq/spark:1.6.0

基于Scala开发的大数据处理框架。与Hadoop不同的是,Hadoop将数据存储到硬盘上,而Spark存储到内存中。

Spark支持SQL查询,流处理,机器学习(Spark MLlib),图处理(Spark GraphX)等。支持Scala,Java,Python,Cljure,R等。

还提供其他库,在大数据分析和机器学习领域提供更多功能。

Storm

baqend/storm:3.4.8
baqend/storm:1.0.0

1
git clone https://github.com/denverdino/docker-storm.git

在Hadoop上加入了实时运算的特性,可以实时处理大数据流。不同于Hadoop和Spark,Storm不进行数据的收集和存储工作,它直接通过网络实时的接受数据并且实时的处理数据,然后直接通过网络实时的传回结果。

Elasticsearch

基于Lucene的开源搜索服务,支持RESTful Web接口。支持实时分布式数据存储和分析查询功能。

编程开发

C / C++

  • GCC: GNU 开发,跨平台编译器的标准。
  • LLVM: Low Level Virtual Machine,提供了一套中立的中间代码和编译基础设施,使得优化可以在编译,连接,运行环境中。
    1
    docker pull imiell/llvm
  • Clang: 基于LLVM的编译器,兼容GCC并准备超过GCC。特性是快,内存占用小,诊断信息可读性强,模块化设计。
    1
    docker pull bowery/clang

Java

  • OpenJDK: 支持JavaSE。
  • Spring Boot: 为了简化Spring应用搭建过程,内嵌Tomcat,Jetty,Undertow,提供starter POM,简化Maven配置,无需XML配置。
    1
    git clone https://github.com/spring-guides/gs-spring-boot-docker.git

Python

  • PyPy: Python实现的Python解释器和即时编译工具,与CPython完全兼容。可以将Python运行速率提高10倍。
  • Flask

Javascript

  • NodeJS: 运行于服务端,采用事件驱动模型,提供多种系统级API。

Golang

1
go get github.com/golang/example/outyet

Linux 命名空间

进程命名空间:

  • 同一进程,在不同空间下看到的进程号不同。
  • 子空间的进程对父空间可见。
  • 容器只能看到docker-containerd进程及以下的进程。

IPC 命名空间:

  • IPC是Linux进程间交互方法:包括信号量,消息队列,共享内存等。
  • 同一IPC命名空间的进程可以彼此交互。

网络命名空间:

  • 提供完全独立的网络协议栈:网络设备接口,IPv4,IPv6,路由表,防火墙规则,sockets等。
  • 网络命名空间之间通过虚拟网卡连接。

挂载命名空间:

  • 可以将一个进程的根文件系统限制在某一个目录下。
  • 类似与chroot。

UTS命名空间:

  • 允许每个容器拥有独立的主机名和域名。
  • Docker默认指定ID前六字节为主机名。

用户命名空间:

  • 一个进程在某个用户命名空间内的用户和组 ID 可以与用户命名空间外的不同。
  • 进程在一个用户命名空间外没有特权,但在这个用户命名空间内有 root 特权。

控制组:

  • 用来对内核资源进行隔离,限制,审计等。
  • 可以限制资源(最大内存),设置优先级,资源审计,隔离,控制等。

Linux 网络虚拟化

运维与监控

Docker 命令行查看

查看容器内状况:

1
docker top container_id

查看节点所有容器情况:

1
docker stats

WeaveScope

WeaveScope Github页面

安装过程:

1
2
3
sudo curl -L git.io/scope -o /usr/local/bin/scope
sudo chmod a+x /usr/local/bin/scope
scope launch

打开4040端口的页面,即可看到WeaveScope。通过这个页面,我们可以可视化管理容器。

添加其他节点到监控中,要在两个节点都执行:

1
scope launch node_1_ip node_2_ip

DevOps

推荐工具:

  • 代码管理
    • Github
    • Gitlab
    • 码云
    • bitbucket
  • 代码持续集成
    • TravisCI
    • GitlabCI
    • Jenkins
  • 代码测试与检查
    • Codecov
    • SonarQube

GitLab

GitLab 官网

GitLab CE 下载页 Ubuntu

Ubuntu 安装 Gitlab

首先开一台虚拟机,用于安装Gitlab(配置要求:推荐4GB内存):

1
2
3
4
5
6
# 安装必要扩展程序
sudo apt-get update
sudo apt-get install -y curl openssh-server ca-certificates
# 安装邮件提醒工具
sudo apt-get install -y postfix
# 在安装过程中会出现配置页面,选择Internet Site,其他默认

下载Gitlab:

安装官方版本:

1
2
curl -s https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.deb.sh | sudo bash
sudo apt-get install gitlab-ee

安装国内镜像版本(仅限Ubuntu 16.04):
在文件/etc/apt/sources.list.d/gitlab-ce.list中写入:

1
deb https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/ubuntu xenial main

再安装Gitlab:

1
2
3
4
# 配置GPG公钥
curl https://packages.gitlab.com/gpg.key 2> /dev/null | sudo apt-key add - &>/dev/null
sudo apt-get update
sudo apt-get install gitlab-ce

安装完成后配置GitLab:

1
2
# 配置GitLab
sudo gitlab-ctl reconfigure

安装完毕后,首次登陆需要设置密码。

配置GitLab,可以到/etc/gitlab/gitlab.rb文件中:

1
2
# URL
external_url ''

修改完成后,让配置生效:

1
sudo gitlab-ctl reconfigure

GitLab CI/CD

CI是代码持续化工具,可以在代码提交后自动化执行代码风格检查,单元测试,项目编译等。当然也可以用Jenkins代替。

安装 Runner

另开一台虚拟机,首先安装Docker:

1
curl -sSL https://get.docker.com/ | sh

安装Gitlab CI Runner:

1
2
3
curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-ci-multi-runner/script.rpm.sh | sudo bash

sudo apt-get install -y gitlab-ci-multi-runner

检查是否安装成功:

1
sudo gitlab-ci-multi-runner status

设置Docker权限:

1
2
3
sudo gpasswd -a gitlab-runner docker
sudo service docker restart
sudo gitlab-ci-multi-runner restart

注册Runner:

1
2
3
4
5
6
7
8
9
10
sudo gitlab-ci-multi-runner register
# 1. 输入 gitlab_ci 的URL
# 2. 输入gitlab-ci的token
# 2.1 进入gitlab中随便打开一个Repository->Setting->CI/CD
# 2.2 进入Runners settings->Setup a specific Runner manually,复制Token
# 3. 输入描述,保存默认即可
# 4. 输入Runner Tags,例如demo,test等
# 5. 是否Run Untagged builds,false
# 6. 是否Lock Runner to current project,false
# 7. 选择执行器,shell

查看Runner:

1
sudo gitlab-ci-multi-runner list

回到Gitlab页面,可以看到runner settings里面多了一个runner。

使用 Runner

一个项目要使用Runner,首先需要在项目下建立文件.gitlab-ci.yml。进入一个项目,选择CI/CD->Pipeline。这时可以看到一个Pipeline。每次提交代码,Pipeline就会自动执行一次,且每一个Stage顺次执行。

使用CI/CD可以自动化执行测试、编译、部署等步骤。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# .gitlab-ci.yml
# 定义阶段
stages:
- build
- test
- deploy
# 定义任务 1
job1:
stage: test
# 指定runner
tags:
- demo
script:
- echo "First"
- echo "Second"
# 定义任务 2
job2:
stage: build
tags:
- demo
script:
- echo "Build First"
- echo "Build Second"
job3:
stage: deploy
tags:
- demo
script:
- echo "Deploy"

检查代码风格

首先注册一个Docker类型的Runner:

1
2
3
4
5
6
7
8
9
10
11
sudo gitlab-ci-multi-runner register
# 1. 输入 gitlab_ci 的URL
# 2. 输入gitlab-ci的token
# 2.1 进入gitlab中随便打开一个Repository->Setting->CI/CD
# 2.2 进入Runners settings->Setup a specific Runner manually,复制Token
# 3. 输入描述,保存默认即可
# 4. 输入Runner Tags,这里是python3.7
# 5. 是否Run Untagged builds,false
# 6. 是否Lock Runner to current project,false
# 7. 选择执行器,docker
# 8. 选择Docker Image,python:3.7

提前把Image拉取到本地:

1
docker pull python:3.7

在项目中加入:

1
2
3
4
5
6
7
8
9
10
# .gitlab-ci.yml
stages:
- stype
job1:
stage: stype
tags:
- python3.7
script:
- pip install tax
- tax -e pep8

自动化部署

CD是持续化部署,即在原来的基础上再进行项目部署。例如有一个项目,经过代码检查后做部署:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# .gitlab-ci.yml
stages:
- stype
- deploy
job1:
stage: stype
tags:
- python3.7
script:
- pip install tax
- tax -e pep8
deploy-job:
stage: deploy
script:
- docker build -t program .
- if [$(docker ps -ap --filter name=web)]; then docker rm -f web;fi
- docker run -d --name web -p 8030:5000 program
tags:
- demo # 这个demo是那个runner
# 只有master分支变化才执行该job
only:
- master

如果风格检查不能通过的话,部署是无法进行的。

另外,为了不让用户每一次push代码都触发一次CI/CD,我们可以把master分支锁定住,防止任何人提交。任何人必须在分支中改动。

在Gitlab中配置:
Repository->Protected Branches->master分支->Allowed to push 改为No one。

General->Merge request settings->Only allow merge requests to be merged if the pipeline succeeds.

用户在提交代码后,进入Gitlab平台,填写Merge Request,等待Pipeline通过。

管理员用户在收到Merge Requst后,则可以执行Merge操作,部署将自动执行。

版本发布

首先搭建一个Docker Registry,类似于Docker Hub。

这里创建第三个服务器,作为Docker Host。首先安装Docker,然后执行:

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

回到Gitlab-CI服务器上,编辑/etc/docker/daemon.json文件:

1
2
3
{
"insecure-registries": ["Docker_Registry_IP:5000"]
}

重启Gitlab-CI服务器上的Docker服务:

1
2
sudo systemctl daemon-reload
sudo systemctl restart docker

再从Docker Hub拉取一个镜像:

1
2
3
4
# 拉取busybox
docker pull busybox
# 为其打上标签
docker tag busybox Docker_Registry_IP:5000/busybox

再Push到我们的私有仓库:

1
docker push Docker_Registry_IP:5000/busybox

如果这些步骤成功,则说明配置成功了。

下面修改文件.gitlab-ci.yml,增加一个Release操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# .gitlab-ci.yml
stages:
- stype
- deploy
- release
job1:
stage: stype
tags:
- python3.7
script:
- pip install tax
- tax -e pep8
# 当代码库新增一个Tag,该任务不执行
except:
- tags
deploy-job:
stage: deploy
script:
- docker build -t program .
- if [$(docker ps -ap --filter name=web)]; then docker rm -f web;fi
- docker run -d --name web -p 8030:5000 program
tags:
- demo
only:
- master

# 增加Release操作
release-job:
stage: release
script:
- docker build -t Docker_Registry_IP:5000/demo:$CI_COMMIT_TAG
- docker push Docker_Registry_IP:5000/demo:$CI_COMMIT_TAG
tags:
- demo
# 只有代码库新增一个Tag,就会触发一次Release
only:
- tags

$CI_COMMIT_TAG是Gitlab定义的环境变量,是版本的标签,可以通过Git工具提交代码时添加新的Tag。

其他Docker尝试

Docker GPU

(Docker GPU)[https://blog.opskumu.com/docker-gpu.html]

Docker Gitlab

(Docker Gitlab)[https://www.jianshu.com/p/080a962c35b6]

安装Gitlab:

1
2
3
4
# 拉取镜像
docker pull gitlab/gitlab-ce
# 运行容器
docker run -d -p 8443:443 -p 8030:80 -p 32222:22 --name gitlab --restart always -v ~/gitlab/config:/etc/gitlab -v ~/gitlab/logs:/var/log/gitlab -v ~/gitlab/data:/var/opt/gitlab gitlab/gitlab-ce

配置Gitlab文件./gitlab/config/gitlab.rb

1
2
3
4
5
6
# 配置http协议所使用的访问地址
external_url 'http://192.168.199.231:8030'

# 配置ssh协议所使用的访问地址和端口
gitlab_rails['gitlab_ssh_host'] = '192.168.199.231'
gitlab_rails['gitlab_shell_ssh_port'] = 32222 # 此端口是run时22端口映射的32222端口

配置完成后,重启Gitlab:

1
docker restart gitlab

Docker DNS

拉取镜像

1
docker pull dns-server

其他机器启动时可以使用参数--dns指定DNS容器。

1
docker run --dns=xx.xx.xx.xx images

Docker Machine

在安装Docker Toolbox时,也自动安装了docker machine。利用docker machine可以创建虚拟机(借助VirtualBox),这个虚拟机小巧且精简,附带Docker,但是功能有限。用户如果想要更全功能的Linux虚拟机,可以使用后面的Vagrant。

创建虚拟机

创建虚拟机:

1
docker-machine create name

常用命令

1
2
3
4
docker-machine ls        # 列出所有虚拟机
docker-machine ssh name # 登录虚拟机
docker-machine stop name # 停止虚拟机
docker-machine env name # 查看环境变量

切换Docker服务端

默认情况下,我们在开机后初次运行Docker Quickstart Terminal时,它就会为我们启动一个虚拟机,这个虚拟机包含了一个Docker Server,所以 这里不必切换Docker服务端

利用Docker Machine可以实现切换Docker Server。如果不想把自己本地的计算机搞乱,我们可以直接安装Docker Client,而服务端通过其他方式创建,也可以正常使用。

切换本地Docker服务端

首先利用上面的docker-machine命令创建带docker server的虚拟机,然后执行下面的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
# 使用命令查看虚拟机的环境变量
docker-machine env name
# 之后会得到虚拟机的环境变量以及一条命令
# eval $(docker-machine env name)

# 执行这条语句,我们就切换了Docker服务端
eval $(docker-machine env name)

# 执行这条语句取消切换
docker-machine env --unset
# 之后会得到一条命令,我们执行它
# eval $(docker-machine env --unset)
eval $(docker-machine env --unset)

切换阿里云Docker服务端

使用阿里云的ECS,首先得保证账号内有钱。其他云平台道理相同。

官方参考页面

进入官方参考页面,点击第三方驱动插件(3rd-party driver plugins),就可以找到Aliyun ECS(点击进入)

首先是下载驱动,解压文件,修改得到的文件的后缀为.exe,并将驱动的所在目录添加到环境变量。

打开阿里云平台,管理控制台,访问控制,用户管理,创建AccessKey和Secret。 注意 :Secret只显示一次,一定要保存好。

回到本地命令行,执行:

1
2
3
docker-machine create -d aliyunecs --aliyunecs-io-optimized=optimized --aliyunecs-instance-type=ecs.c5.large --aliyunecs-access-key-id=用户ID --aliyunecs-access-key-secret=用户Secret --aliyunecs-region=cn-qingdao 虚拟机name

# 这些参数的值可以在阿里云平台上的创建页面查看。

创建好以后,就可以按照上一节的内容切换服务端了。

Vagrant(可选)

Vagrant是一个虚拟机管理工具。可以用脚本的方式快速创建虚拟机(需要安装VirtualBox)以及虚拟机集群。如果不想直接在自己的电脑上安装Docker,可以尝试Vagrant。

Vagrant官网
Vagrant下载页
VirtualBox官网

Vagrant 镜像

官方镜像

首先进入Vagrant官网,点击Find Boxes,输入要查找的虚拟机。例如输入Unbutu 16.04,搜索后进入,选择New,会出现如下页面:

1
2
3
4
# 创建Vagrantfile
vagrant init ubuntu/xenial64
# 执行Vagrantfile
vagrant up

执行页面中提示的操作,Vagrant会在当前目录创建该虚拟机。

非官方镜像

如果上面的过程太漫长,可以自定义添加Box。打开vagrantbox.es,按照上面的提升操作添加虚拟机镜像。

例如:

1
2
3
vagrant box add ubuntu_new https://atlas.hashicorp.com/envimation/boxes/ubuntu-xenial-docker
vagrant init ubuntu_new
vagrant up

如果命令行下载速度较慢,可以使用迅雷等工具下载到本地,再使用:

1
2
3
vagrant box add ubuntu_new /path/to/box
vagrant init ubuntu_new
vagrant up

也可以到清华镜像下载,选择目录中64位vagrant.box文件即可。
清华镜像源

Ubuntu 各个版本编号

版本号 代号
16.04 Xenial
18.04 Bionic
19.04 Disco
20.04 Focal

Vagrant SSH

在虚拟机目录下执行:

1
vagrant ssh

即可进入虚拟机。

Vagrant 常用命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
vagrant box add   # 添加一个box
vagrant box list # 列出所有的box
vagrant box remove # 删除box
vagrant init ubuntu/trustry64 # 初始化一个新的虚拟机

vagrant up # 启动虚拟机
vagrant ssh # 登录虚拟机
vagrant halt # 关闭虚拟机
vagrant reload # 重启虚拟机
vagrant suspend # 挂起虚拟机
vagrant resume # 恢复挂起
vagrant destory [name|id] # 销毁虚拟机

vagrant package # 打包当前虚拟机到box
vagrant global-status # 查看当前所有虚拟机状态
vagrant ssh-config # 查看ssh连接信息

Vagrant 配置文件

通过编辑脚本也可以利用vagrant同时创建多台虚拟机。

我们在使用之前的方法创建虚拟机时,目录下会自动生成Vagrantfile文件,这就是Vagrant虚拟机的配置文件,里面包含虚拟机的配置,SSH的配置以及Vagrant的基础配置,这些配置是基于Ruby语法的。

单机模式配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# box
config.vm.box = "CentOs7"

# hostname
config.vm.hostname = "for_work"

# 虚拟机网络设置
# 虚拟机网络有两种连接方式:
# 主机模式(host-only),虚拟机只能和主机通信,其他人无法访问到虚拟机。
# 桥接模式(bridge),虚拟机成为同主机在同一局域网下的独立主机。
config.vm.network "private_network", ip: "192.168.33.10"
#config.vm.network "public_network"

# 同步目录设置
config.vm.synced_folder "/Users/path/www", "/vagrant"

# 端口转发设置
config.vm.network :forwarded_port, guest: 80, host: 80

# 配置多行脚本
config.vm.provision "shell", inline: <<-SHELL
sudo apt-get update
SHELL

参考脚本配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# 例 1:
Vagrant.configure("2") do |config|
config.vm.define :web do |web|
web.vm.provider "virtualbox" do |v|
v.customize ["modifyvm", :id, "--name", "web", "--memory", "512"]
end
web.vm.box = "base_box"
web.vm.hostname = "web"
web.vm.network :private_network, ip: "192.168.33.10"
end

config.vm.define :redis do |redis|
redis.vm.provider "virtualbox" do |v|
v.customize ["modifyvm", :id, "--name", "redis", "--memory", "512"]
end
redis.vm.box = "base_box"
redis.vm.hostname = "redis"
redis.vm.network :public_network, ip: "192.168.33.11"
end
end

# 例 2:
Vagrant.require_version ">=1.6.0"

boxes = [
{
:name => "node1",
:eth1 => "192.168.205.10",
:mem => "1024",
:cpu => "1",
:port => "8888"
},
{
:name => "node2",
:eth1 => "192.168.205.11",
:mem => "1024",
:cpu => "1",
:port => "9999"
}
]

Vagrant.configure(2) do |config|
config.vm.box = "base_box"
boxes.each do |opts|
config.vm.define opts[:name] do |config|
config.vm.hostname = opts[:name]
config.vm.provider "vmware_fusion" do |v|
v.vmx["memsize"] = opts[:mem]
v.vmx["numvcpus"] = opts[:cpu]
end

config.vm.provider "virtualbox" do |v|
v.customize ["modifyvm", :id, "--memory", opts[:mem]]
v.customize ["modifyvm", :id, "--cpus", opts[:cpu]]
end

config.vm.network :private_network, ip: opts[:eth1]
# config.vm.network :public_network
config.vm.network "forwarded_port", guest: 80, host: 8050
end
end

config.vm.synced_folder "./labs", "/home/vagrant/labs" # 本地需要手动创建同步目录
config.vm.provision "shell", privileged: true, path: "./setup.sh" # 初始化脚本,包括更换源,安装Docker,参考下面的编写
end

参考文档

配置一个带Docker的Vagrant虚拟机

创建一个目录,并配置如下文件:

  • VirtualBox
    • labs
      • sources.list
    • setup.sh

编辑setup.sh如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
sudo cp /etc/apt/sources.list /etc/apt/sources.list.back
sudo rm /etc/apt/sources.list
sudo touch /etc/apt/sources.list
sudo cp /home/vagrant/labs/sources.list /etc/apt/sources.list
sudo apt-get remove docker docker-engine docker.io containerd runc
sudo apt-get update
sudo apt-get install -y \
apt-transport-https \
ca-certificates \
curl \
gnupg-agent \
software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo apt-key fingerprint 0EBFCD88
sudo add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) \
stable"
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io
sudo mkdir -p /etc/docker
sudo gpasswd -a ubuntu docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://sfpj1t4c.mirror.aliyuncs.com"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
sudo service docker restart
sudo reboot

编辑sources.list内容如下(适用于ubuntu 16.04,其他版本可以自行搜索):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
deb http://mirrors.aliyun.com/ubuntu/ xenial main
deb-src http://mirrors.aliyun.com/ubuntu/ xenial main

deb http://mirrors.aliyun.com/ubuntu/ xenial-updates main
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-updates main

deb http://mirrors.aliyun.com/ubuntu/ xenial universe
deb-src http://mirrors.aliyun.com/ubuntu/ xenial universe
deb http://mirrors.aliyun.com/ubuntu/ xenial-updates universe
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-updates universe

deb http://mirrors.aliyun.com/ubuntu/ xenial-security main
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-security main
deb http://mirrors.aliyun.com/ubuntu/ xenial-security universe
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-security universe

在VirtualBox下执行

1
vagrant init

会生成Vagrantfile文件。打卡文件,编辑:

1
2
3
4
5
6
7
8
9
10
# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|
config.vm.box = "box_base"
config.vm.network "forwarded_port", guest: 80, host: 8030
config.vm.network "public_network"
config.vm.synced_folder "./labs", "/home/vagrant/labs"
config.vm.provision "shell", privileged: true, path: "./setup.sh"
end

保存后:

1
vagrant up

即可创建虚拟机。输入如下命令登录虚拟机:

1
vagrant ssh

安装扩展

1
2
3
4
5
6
# 查看插件列表
vagrant plugin list
# 安装插件
vagrant plugin install vagrant-scp
# 使用 scp 复制文件到虚拟机
vagrant scp ../node3/labs/ docker-node1:/home/vagrant/labs/

参考文档

Dockerfile 语法

命令

FROM

定制的镜像都是基于 FROM 的镜像,这里的 nginx 就是定制需要的基础镜像。后续的操作都是基于 nginx。

1
2
3
FROM scratch      # 制作base image
FROM ubuntu # 使用base image
FROM ubuntu:14.04 # 使用特定版本的image

LABEL

相当于注释:

1
2
3
LABEL maintainer="author@web.com"
LABEL version="1.0"
LABEL description="The Description"

RUN

用于执行后面跟着的命令行命令。

1
2
3
4
5
6
7
8
9
10
11
12
FROM nginx
RUN echo '这是一个本地构建的nginx镜像' > /usr/share/nginx/html/index.html

# RUN 有以下俩种格式:
# 1. shell 格式:
RUN <命令行命令>
# <命令行命令> 等同于,在终端操作的 shell 命令。

# 2. exec 格式:
RUN ["可执行文件", "参数1", "参数2"]
# 例如:
# RUN ["./test.php", "dev", "offline"] 等价于 RUN ./test.php dev offline

另外,Dockerfile 的指令每执行一次都会在 docker 上新建一层。所以过多无意义的层,会造成镜像膨胀过大。例如:

1
2
3
4
5
6
7
8
9
10
FROM centos
RUN yum install wget
RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz"
RUN tar -xvf redis.tar.gz
# 以上执行会创建 3 层镜像。可简化为以下格式:
FROM centos
RUN yum install wget \
&& wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz" \
&& tar -xvf redis.tar.gz
# 以 && 符号连接命令,这样执行后,只会创建 1 层镜像。

编辑保存之后就可以构建Docker镜像了。

1
docker build -t nginx:test .

CMD

类似于 RUN 指令,用于运行程序,但二者运行的时间点不同:

  • CMD 在docker run 时运行。
  • RUN 是在 docker build用的。

作用:为启动的容器指定默认要运行的程序,程序运行结束,容器也就结束。CMD 指令指定的程序可被 docker run 命令行参数中指定要运行的程序所覆盖。

注意:如果 Dockerfile 中如果存在多个 CMD 指令,仅最后一个生效;如果在docker run中指明了命令,CMD就会被忽略。

1
2
3
4
5
# Shell 格式
CMD <shell 命令>
# Exec 格式
CMD ["<可执行文件或命令>","<param1>","<param2>",...]
CMD ["<param1>","<param2>",...] # 该写法是为 ENTRYPOINT 指令指定的程序提供默认参数

ENTRYPOINT

ENTRYPOINT会让容器以应用程序或服务的方式运行。它类似于 CMD 指令,但其不会被 docker run 的命令行参数指定的指令所覆盖,而且这些命令行参数会被当作参数送给 ENTRYPOINT 指令指定的程序。

但是, 如果运行 docker run 时使用了 –entrypoint 选项,此选项的参数可当作要运行的程序覆盖 ENTRYPOINT 指令指定的程序。

优点:在执行 docker run 的时候可以指定 ENTRYPOINT 运行所需的参数。

注意:如果 Dockerfile 中如果存在多个 ENTRYPOINT 指令,仅最后一个生效。

1
ENTRYPOINT ["<executeable>","<param1>","<param2>",...]

可以搭配 CMD 命令使用:一般是变参才会使用 CMD ,这里的 CMD 等于是在给 ENTRYPOINT 传参,以下示例会提到。

示例:

假设已通过 Dockerfile 构建了 nginx:test 镜像:

1
2
3
4
FROM nginx

ENTRYPOINT ["nginx", "-c"] # 定参
CMD ["/etc/nginx/nginx.conf"] # 变参
1
2
3
4
5
6
7
8
9
# 1、不传参运行
docker run nginx:test
# 容器内会默认运行以下命令,启动主进程。
# nginx -c /etc/nginx/nginx.conf

# 2、传参运行
docker run nginx:test -c /etc/nginx/new.conf
# 容器内会默认运行以下命令,启动主进程(/etc/nginx/new.conf:假设容器内已有此文件)
# nginx -c /etc/nginx/new.conf

WORKDIR

指定工作目录。用 WORKDIR 指定的工作目录,会在构建镜像的每一层中都存在。(WORKDIR 指定的工作目录,必须是提前创建好的)。

docker build 构建镜像过程中的,每一个 RUN 命令都是新建的一层。只有通过 WORKDIR 创建的目录才会一直存在。

1
2
WORKDIR <工作目录路径>
WORKDIR /test # 会自动创建

COPY

复制指令,从上下文目录中复制文件或者目录到容器里指定路径。

1
2
3
4
5
6
7
8
9
10
COPY [--chown=<user>:<group>] <源路径1>...  <目标路径>
COPY [--chown=<user>:<group>] ["<源路径1>",... "<目标路径>"]

# [--chown=<user>:<group>]:可选参数,用户改变复制到容器内文件的拥有者和属组。
# <源路径>:源文件或者源目录,这里可以是通配符表达式,其通配符规则要满足 Go 的 filepath.Match 规则。例如:

COPY hom* /mydir/
COPY hom?.txt /mydir/

# <目标路径>:容器内的指定路径,该路径不用事先建好,路径不存在的话,会自动创建。

ADD

ADD 指令和 COPY 的使用格式一致(同样需求下,官方推荐使用 COPY)。功能也类似,不同之处如下:

  • ADD 的优点:在执行 <源文件> 为 tar 压缩文件的话,压缩格式为 gzip, bzip2 以及 xz 的情况下,会自动复制并解压到 <目标路径>。
  • ADD 的缺点:在不解压的前提下,无法复制 tar 压缩文件。会令镜像构建缓存失效,从而可能会令镜像构建变得比较缓慢。具体是否使用,可以根据是否需要自动解压来决定。
1
2
3
4
5
WORKDIR /root
ADD main test/ # /root/test/main

WORKDIR /ROOT
COPY main test/

如果想要添加远程文件,还是要用命令的方式(curl、wget)较好:

ENV

设置环境变量,定义了环境变量,那么在后续的指令中,就可以使用这个环境变量。

1
2
3
4
5
6
7
8
9
10
11
ENV <key> <value>
ENV <key1>=<value1> <key2>=<value2>...

# 例如
ENV NODE_VERSION 7.2.0

RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz" \
&& curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc"

# 不加/bin/bash,会导致无法识别ENV变量
RUN ["/bin/bash", "-c", "echo $ENV_KEY"]

ARG

构建参数,与 ENV 作用一至。不过作用域不一样。ARG 设置的环境变量仅对 Dockerfile 内有效,也就是说只有 docker build 的过程中有效,构建好的镜像内不存在此环境变量。

构建命令 docker build 中可以用 –build-arg <参数名>=<值> 来覆盖。

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

VOLUME

定义匿名数据卷。在启动容器时忘记挂载数据卷,会自动挂载到匿名卷,可以通过 -v 参数修改挂载点。

作用:

  • 避免重要的数据,因容器重启而丢失,这是非常致命的。
  • 避免容器不断变大。
1
2
VOLUME ["<路径1>", "<路径2>"...]
VOLUME <路径>

EXPOSE

仅仅只是声明端口。

作用:

  • 帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射。
  • 在运行时使用随机端口映射时,也就是 docker run -P 时,会自动随机映射 EXPOSE 的端口。
1
EXPOSE <端口1> [<端口2>...]

USER

用于指定执行后续命令的用户和用户组,这边只是切换后续命令执行的用户(用户和用户组必须提前已经存在)。

1
USER <用户名>[:<用户组>]

HEALTHCHECK

用于指定某个程序或者指令来监控 docker 容器服务的运行状态。

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

HEALTHCHECK [选项] CMD <命令> : 这边 CMD 后面跟随的命令使用,可以参考 CMD 的用法。

ONBUILD

用于延迟构建命令的执行。简单的说,就是 Dockerfile 里用 ONBUILD 指定的命令,在本次构建镜像的过程中不会执行(假设镜像为 test-build)。当有新的 Dockerfile 使用了之前构建的镜像 FROM test-build ,这是执行新镜像的 Dockerfile 构建时候,会执行 test-build 的 Dockerfile 里的 ONBUILD 指定的命令。

1
ONBUILD <其它指令>

总结

  1. 应使用LABEL注明文件作用以及内容;
  2. 应使用ENV注明主要变量,方便维护;
  3. 应当使用WORKDIR注明工作路径,且推荐使用绝对路径;
  4. 应使用COPY为主,需要解压时可以使用ADD;
  5. 应当在使用RUN时,尽量合并命令,防止产生多个层;
  6. 应当在使用Exec格式时使用”/bin/bash -c”来识别ENV变量,且被执行的指令应放到一起;

参考 菜鸟教程

YAML 语法

YAML脚本的后缀为yml或yaml,基本语法如下:

  • 大小写敏感;
  • 使用缩进表示层级关系;
  • 缩进不允许使用tab,只允许空格;
  • 缩进的空格数不重要,只要相同层级的元素左对齐即可;
  • ‘#’表示注释。

YMAL 支持三种数据类型:

  • 对象:键值对的集合,又称为映射(mapping)/ 哈希(hashes) / 字典(dictionary)
  • 数组:一组按次序排列的值,又称为序列(sequence) / 列表(list)
  • 纯量(scalars):单个的、不可再分的值

YAML 对象

对象键值对使用冒号结构表示 key: value,冒号后面要加一个空格。

也可以使用 key:{key1: value1, key2: value2, …}。

还可以使用缩进表示层级关系;

1
2
3
key: 
child-key: value
child-key2: value2

较为复杂的对象格式,可以使用问号加一个空格代表一个复杂的key,配合一个冒号加一个空格代表一个value,意思即对象的属性是一个数组[complexkey1,complexkey2],对应的值也是一个数组[complexvalue1,complexvalue2]:

1
2
3
4
5
6
?  
- complexkey1
- complexkey2
:
- complexvalue1
- complexvalue2

YAML 数组

- 开头的行表示构成一个数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 多行表示
- A
- B
- C
# 行内表示:
key: [value1, value2, ...]
# 数据结构的子成员是一个数组,则可以在该项下面缩进一个空格。
-
- A
- B
- C

# 一个相对复杂的例子:
companies:
-
id: 1
name: company1
price: 200W
-
id: 2
name: company2
price: 500W
# 也可以表示为
companies: [{id: 1,name: company1,price: 200W},{id: 2,name: company2,price: 500W}]

复合结构

数组和对象可以构成复合结构,例:

1
2
3
4
5
6
7
8
9
languages:
- Ruby
- Perl
- Python
websites:
YAML: yaml.org
Ruby: ruby-lang.org
Python: python.org
Perl: use.perl.org

纯量

  • 字符串
  • 布尔值
  • 整数
  • 浮点数
  • Null
  • 时间
  • 日期

表示方法如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
boolean: 
- TRUE #true,True都可以
- FALSE #false,False都可以
float:
- 3.14
- 6.8523015e+5 #可以使用科学计数法
int:
- 123
- 0b1010_0111_0100_1010_1110 #二进制表示
null:
nodeName: 'node'
parent: ~ #使用~表示null
string:
- 哈哈
- 'Hello world' #可以使用双引号或者单引号包裹特殊字符
- newline
newline2 #字符串可以拆成多行,每一行会被转化成一个空格
date:
- 2018-02-17 #日期必须使用ISO 8601格式,即yyyy-MM-dd
datetime:
- 2018-02-17T15:02:31+08:00 #时间使用ISO 8601格式,时间和日期之间使用T连接,最后使用+代表时区

引用

& 用来建立锚点,<< 表示合并到当前数据,* 用来引用锚点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
defaults: &defaults
adapter: postgres
host: localhost

development:
database: myapp_development
<<: *defaults

test:
database: myapp_test
<<: *defaults

# 相当于

defaults:
adapter: postgres
host: localhost

development:
database: myapp_development
adapter: postgres
host: localhost

test:
database: myapp_test
adapter: postgres
host: localhost

参考 菜鸟教程