
Docker

基于 golang 开发的容器化技术 docker
Docker
docker 核心技术
隔离性 Linux namespace
每个用户实例之间隔离,互不影响
pid namespace
隔离不同用户的进程,并且不同的namespace可以有相同的pid。
net namespace
实现网络隔离,默认通过veth的方式将container中的虚拟网卡同host上的docker bridge: docker0连接
ipc namespace
进程交互,在IPC资源申请时加入namespace信息-每个IPC资源都有一个唯一的32位ID。
mnt namespace
允许不同namespace的进程看到的文件结构不同,文件目录被隔离开。
uts namespace
允许每个容器有独立的host name和domain name,使其在网络上可被视为一个独立的结点而非host上的一个进程
user namespace
不同容器拥有不同user和group id。
控制组 Cgroups
控制组 – 可配额、可度量
在/cgroup目录下新建一个文件夹即可新建一个group,在此文件夹中新建task文件,并将pid写入该文件,即可实现对资源的控制。
- blkio限制每个块设备的输入输出控制。光盘、磁盘、usb等。
- cpu使用调度程序为cgroup任务提供cpu的访问
- cpuacct产生cgroup任务的cpu资源报告
- cpuset针对多核心cpu,会为cgroup任务分配单独的cpu和内存
- devices允许或拒绝cgroup任务对设备的访问
- freezer暂停或恢复cgroup任务
- memory设置每个cgroup内存限制以及内存资源报告
- net_cts标记每个网络包供cgroup使用
- ns名称空间子空间
便携性:AUFS
支持将不同目录挂载在同一虚拟文件系统下的文件系统
支持为每一个成员目录(类似于Git branch)设置读写权限
AUFS中有类似分层的概念,对readonly权限的branch可以逻辑上进行修改(增量的,不影响readonly部分的)
实现不借助LVM,RAID将多个disk挂载在同一目录下
讲一个readonly的branch和一个writeable的branch联合在一起,live CD正是基于此方法可以在OS image不变的基础上允许用户在其上进行写操作
典型的启动Linux运行需要两个FS:bootfs,rootfs
bootfs(lxc, aufs/btrfs, kernel) -> rootfs(/dev, /proc, /bin, /etc, /usr, /tmp …)
- bootfs(boot file system)主要包含bootloader和kernel,bootloader主要引导加载kernel,当boot成功后kernel被加载到内存中后bootfs被umount。
- rootfs(root file system)包含典型Linux系统中的/dev, /proc, /bin, /etc等标准目录和文件
- readonly方式加载并检查,然后利用union mount的方式将readwrite文件系统挂载在readonly的rootfs上,并且允许叠加;这样一组readonly和一个writeable的结构构成一个container运行时态,每个FS被称为一个FS层。
由于AUFS,每一个对readonly层文件、目录的修改都只会存在于上层的writeable层中。这样由于不存在竞争,多个container可以共享readonly的FS层。所以Docker将readonly的FS层称为”image” - 对于container而言整个rootfs都是read-write的,但事实上所有的修改都写入最上层的writeable层中,image不保护用户状态,只用于模板,新建和复制使用。
上层的image依赖下层的image,因此在Docker中把下层的image称为父image,没有父image的image称为base image。因此想要从一个image中启动一个container,Docker会先加载这个image依赖的父image以及base image;用户的进程运行在writeable的layer中。所有的parent image中的数据信息以及ID、网络和lxc管理的资源限制等具体container的配置,构成一个Docker概念上的container。
安全性 AppArmor,SELinux,GRSEC
- 由kernel namespace和cgroup实现的Linux系统固有的安全标准
- Docker Deamon的安全接口
- Linux本身的安全加固解决方案,类似AppArmor,SELinux
Docker 快速入门
Docker安装
docker配置文件
1
2/ect/docker/
/var/lib/docker/安装
1
2sudo pacman -Syu
sudo pacman -S docker运行配置
1
2
3
4
5
6
7
8
9
10
11# 启动docker
sudo systemctl start docker.service
sudo systemctl start docker
# 开机启动
sudo systemctl enable docker.service
# 关闭docker
sudo systemctl stop docker
# 重启docker
sudo systemctl restart docker
# 查看docker状态
sudo systemctl status docker1
2
3
4# 查看version
sudo docker version
# 查看docker信息
sudo docker info不通过root运行docker
1
sudo usermod -aG docker $USER
重启后奏效
Docker镜像
基本结构
基础镜像信息
1
FROM ubuntu
维护者信息
1
MAINTAINER fetch150zy <Mars_zhewei@outlook.com>
镜像操作指令
1
2
3RUN echo "deb http://archive.ubuntu.com/ubuntu/ raring main universe" >> /etc/apt/sources.list
RUN apt-get update && apt-get install -y nginx
RUN echo "\ndaemon off;" >> /etc/nginx/nginx.conf容器启动时执行指令
1
CMD /usr/sbin/nginx
example:
1 | # Nginx |
Docker镜像操作
查找获取镜像
1
2
3
4
5# 查找所需镜像
docker search hello-world
# 拉取镜像
docker pull hello-world
docker image pull hello-world查看已有镜像
1
2
3
4
5
6# 查看镜像
docker images
docker image ls
# 查看镜像历史信息
docker history hello-world:latest
docker image history hello-world:latest备份镜像
1
2docker tag hello-world:latest hello-world:v2.0
docker image tag hello-world:latest hello-world:v1.0删除镜像
1
2docker rmi hello-world:latest
docker image rm hello-world:latest清除未使用的镜像
1
docker image prune
导出导入镜像
1
2
3
4
5
6# 导出tar包
docker save -o hello-world:latest.tar.gz hello-world:latest
docker image save -o hello-world:latest.tar.gz hello-world:latest
# 导入
docker load -i hello-world:latest.tar.gz
docker image load -i hello-world:latest.tar.gz
Dockerfile操作建议
dockerfile尽量越简洁越好
最好把dockerfile放在一个空目录,或者使用.dockerignore文件排除
尽量避免安装不需要的包
一个镜像只跑一个process
在dockerfile可读性和镜像层次最小化之间取得平衡
构建缓存
在构建镜像时,进程将为dockerfile内每一个指定的执行步骤构建一个镜像。
执行指令时会对镜像缓存检查,镜像是可以重复利用的。
如果不想使用缓存,在docker build 时使用 –no-cach=true
- 从缓存中启动一个基础镜像,执行下一条指令,与上一层所有子镜像对比,查看是否与已经存在的镜像相同,如果不同缓存失效
- 大多数只需要简单比较dockerfile指令与其中的一个子镜像就够了
- 对于ADD和COPY指令,镜像中的文件每个的内容将全部检查
- docker会与已经存在的镜像文件进行校验,如果内容或元数据被更改,缓存失效
- ADD和COPY指令缓存将不会查看容器内文件来匹配缓存
镜像文件的大小
dockerfile中的每一条指令都对应Docker镜像中的一层
exp:
1 | FROM ubuntu:14.04 |
构建 Docker 镜像时,对当前的文件系统造成了修改更新
- ADD和COPY命令,在docker build构建镜像时向容器中添加内容。如果内容添加成功,那么构建的那层镜像大小就是添加内容的大小
- RUN命令,如果运行的命令涉及磁盘文件更新,该层镜像大小就是文件系统的增量修改部分
联合文件系统
镜像总大小是每一层镜像大小的总和
- 联合文件系统的性质保证相同文件的镜像层,容器仅能看到一个
镜像共享关系
- 多个不同的docker镜像可以共享相同的镜像层
Dockerfile参数详解
FROM
1
2FROM <image>
FROM <image>:<tag>如果想在同一个dockerfile中创建多个镜像时,可以使用多个FROM指令
MAINTAINER(指定维护者信息)
1
MAINTAINER <name>
RUN(运行命令)
1
2RUN <command>
RUN ["executable", "param1", "param2"]第一个在shell终端中运行命令
第二个使用exec执行,也可以指定其它终端
RUN ["/bin/bash", "-c", "echo hello"]
CMD(容器启动时执行的命令)
1
2
3CMD ["executable","param1","param2"] 使用 exec 执行,推荐方式;
CMD command param1 param2 在 /bin/sh 中执行,提供给需要交互的应用;
CMD ["param1","param2"] 提供给 ENTRYPOINT 的默认参数;每个dockerfile只能有一条CMD命令
EXPOSE(暴露的端口号)
1
EXPOSE <port> [<port>...]
docker主机会自动分配一个端口转发到指定的端口
ENV(指定环境变量)
1
ENV <key> <value>
1
2
3
4ENV PG_MAJOR 9.3
ENV PG_VERSION 9.3.4
RUN curl -SL http://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgress && …
ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH指定使用环境并在容器运行时保持
ADD
1
ADD <src> <dest>
复制到容器中,可以是URL或tar文件(自动解压为目录)
COPY
1
COPY <src> <dest>
复制本地主机内容(Dockerfile所在目录的相对路径)到容器中
当使用本地目录作为源目录时,推荐使用COPY
ENTERPOINT
1
2ENTRYPOINT ["executable", "param1", "param2"]
ENTRYPOINT command param1 param2(shell中执行)。配置容器启动后执行的命令,并且不可被docker run提供的参数覆盖
每个dockerfile只能有一个ENTERPOINT
VOLUME
1
VOLUME ["/data"]
创建一个可以从本地主机或其它容器挂载的挂载点,一般用来存放数据库和需要保持的数据等
USER
1
USER daemon
指定运行容器的用户名或UID,后续的RUN也会使用指定用户
当服务不需要管理员权限时,可以通过该命令指定运行用户,并可以在之前创建所需要的用户
WORKDIR
1
WORKDIR /path/to/workdir
为后续的RUN、CMD、ENTERPOINT指令配置工作目录
1
2
3
4
5WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
# 最终路径为 /a/b/cONBUILD
1
ONBUILD [INSTRUCTION]
配置当所创建的镜像作为其它新创建镜像的基础镜像时,所执行的操作指令
exp:
1
2
3
4[...]
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src
[...]基于上面创建的镜像来创建新的镜像
1
2
3
4FROM image-A
#Automatically run the following
ADD . /app/src
RUN /usr/local/bin/python-build --dir /app/src
Docker容器
Docker容器操作
查看所有容器
1
2
3
4docker ps # 默认只显示启动的容器
docker ps -a # 显示所有容器状态
docker ps -q # 查看容器编号
docker ps -l # 展示最近一次创建的容器状态守护进程启动容器
1
docker run -d --name ngx nginx:latest
启动容器
1
2
3
4
5docker start ngx
# 也可一次启动多个容器
docker start ngx ubuntu redis
# 重新启动容器
docker restart ngx停止容器
1
2
3
4
5
6docker stop ngx
docker container stop ngx
# 也可一次停止多个容器
docker stop ngx ubuntu redis
# sigkill
docker kill ngx删除容器
1
docker rm ngx
批量删除正在运行的容器
1
2docker rm $(docker ps -q) -f
-f 强制创建容器并进入
1
2
3
4
5
6docker run -it --name ngx -P nginx:latest /bin/bash
- i 交互
- t tty
-- name 指定容器名
- P 随机分配一个主机端口到容器端口的映射
- p 指定端口映射 exp: 5000:5000重新进入容器
1
docker exec -it ngx /bin/bash
通过容器创建镜像
1
docker commit -m "message" -a "author" ngx nginx:v1.0
查看镜像容器全部信息
1
2
3docker image inspect nginx:latest
docker container inspect ngx
-f 参数可以筛选出想要的信息打印容器操作事件
1
2
3docker events -f 'event=stop' --since '3m'
-f 过滤
--since 按时间筛选查看容器网络端口
1
docker port app 5000
查看容器中应用服务进程
1
2
3$ docker top app
PID USER COMMAND
854 root python app.py
容器入门
绑定容器到另外一个主机/端口或者一个Unix Socket
使用 -H 能够让Docker daemon监听特殊的IP和端口
默认情况下监听unix:///var/run/docker.sock以仅允许root进行本地连接。你可以将他设置为0.0.0.0:2375或者是指定的主机IP以供所有人连接,但是不建议这么做
类似的,Docker客户端也可以使用 -H 连接一个指定端口
-H 使用以下格式来分配主机和端口
tcp://[host][:port][path]
orunix://path
1 | tcp://host:2375 -> TCP connection on host:2375 |
Docker以daemon模式运行
1 | $ sudo <path to>/docker daemon -H 0.0.0.0:5555 & |
起一个持续工作的进程
1 | $ JOB=$(docker run -d ubuntu /bin/sh -c "while true; do echo Hello world; sleep 1; done") |
在一个TCP端口上绑定一个服务
1 | # Bind port 4444 of this container, and tell netcat to listen on it |
管理容器的数据
docker 管理数据主要有两种方式,数据卷以及数据卷容器
数据卷是在一个或多个容器,它绕过UFS的一个专门指定的目录。数据卷为持续共享数据提供了一些有用的功能
- 在创建容器时,卷被初始化。如果容器的基础映像包含指定的数据装入点,现有的数据复制到在卷初始化新卷
- 数据卷可以共享和容器之间重复使用
- 改变数据卷将立刻生效
- 改变数据卷数据不会影响到容器
- 即使容器本身被删除,但是数据卷依然存在
- 数据卷的目的是持久化数据,独立于容器的声明周期。Docker因此不会自动删除卷,当你删除一个容器,也不会“垃圾回收”直到没有容器再使用
添加一个数据卷
docker run
加上- v
参数来添加一个数据卷,- v
参数可以多次使用来挂载多个数据卷1
$ docker run -d -P --name web -v /webapp training/webapp python app.py
这条命令在容器中的/webapp文件夹下创建一个数据卷存储数据
你也可以在构建镜像时在dockerfile里面定义
数据卷默认权限是读写,你也可以设置为只读
1
$ docker run -d -P --name web -v /opt/webapp:ro training/webapp python app.py
查找数据卷路径用
docker inspect
命令定位1
$ docker inspect web
1
2
3
4
5
6
7
8
9
10
11
12...
Mounts": [
{
"Name": "fac362...80535",
"Source": "/var/lib/docker/volumes/fac362...80535/_data",
"Destination": "/webapp",
"Driver": "local",
"Mode": "",
"RW": true
}
]
# 可以看见权限RW为true挂载一个主机目录作为数据卷
挂载主机目录为数据卷,必须参照
-v hostPath:containerPath
这种格式,路径必须为绝对路径,以保证容器的可移植性1
2
3
4$ docker run -d -P --name web -v /src/webapp:/opt/webapp training/webapp python app.py
# 加载主机的 /src/webapp 目录到容器的 /opts/webapp 目录
# docker 数据卷的权限为读写,你也可以设置为只读
$ docker run -d -P --name web -v /src/webapp:/opt/webapp:ro training/webapp python app.py挂载一个数据文件作为数据卷
- v
标记也可以从主机挂载单个文件到容器中1
2$ sudo docker run --rm -it -v ~/.bash_history:/.bash_history ubuntu /bin/bash
# 这样可以记录在容器中输入过的命令了如果想挂载一个文件,最简单的方式是直接挂载文件的父目录
创建和挂载数据卷容器
如果你有一些持续更新的数据想在容器间共享,最好创建数据卷容器
数据卷容器其实就是一个正常的容器,专门用来提供数据卷供其它容器挂载的
1
2
3
4
5
6
7# 创建一个命名的数据卷容器dbdata
$ sudo docker run -d -v /dbdata --name dbdata training/postgres echo Data-only container for postgres
# 然后在其它容器中使用 --volume-from 来挂载dbdata容器中的数据卷
$ sudo docker run -d --volumes-from dbdata --name db1 training/postgres
$ sudo docker run -d --volumes-from dbdata --name db2 training/postgres
# 还可以使用 --volume-from 参数来从多个容器挂载多个数据卷,也可以从其它已经挂载了数据卷的容器来挂载数据卷
$ sudo docker run -d --name db3 --volumes-from db1 training/postgres注意:使用 –volumes-from 参数所挂载数据卷的容器自己并不需要保持在运行状态
如果删除了挂载的容器(包括dbdata, db1, db2),数据卷并不会被自动删除。如果要删除一个数据卷,必须在删除最后一个还挂载着它的容器时使用 docker rm -v 命令来指定同时删除关联的容器。 这可以让用户在容器之间升级和移动数据卷
备份、存储、移动数据卷
备份
1
$ docker run --volumes-from dbdata -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar /dbdata
创建新容器
1
$ docker run -v /dbdata --name dbdata2 ubuntu /bin/bash
解压数据卷挂载到容器
1
$ docker run --volumes-from dbdata2 -v $(pwd):/backup ubuntu cd /dbdata && tar xvf /backup/backup.tar
管理容器的通信
容器与宿主机采用端口映射的方式通信
1
2
3
4
5
6# 启动容器 -P 随机分配端口
$ docker run -d -P training/webapp python app.py
# 查看端口映射状态
$ docker ps nostalgic_morse
# 使用 -p 指定端口映射
$ docker run -d -p 80:5000 training/webapp python app.py- p
参数的其它指定方法1
2
3ip:hostPort:containerPort 映射指定IP的指定端口
ip::containerPort 映射指定IP任意端口
hostPort:containerPort。 映射所有主机IP的指定端口采用link方式通信
为了方便建立连接,通常需要为容器起一个name,如果不起,你会发现系统会自动分配一个名字
1
$ docker run -d -P --name web training/webapp python app.py
links允许容器发现另一个容器,并在期间建立一个安全的通道以便交换数据,使用–link参数来创建一个连接
1
2
3$ docker run -d --name db training/postgres
# 创建一个web连接到db数据库容器
$ docker run -d -P --name web --link db:db training/webapp python app.py参数格式如下:
--link <name or id>:alias
alias代表你为这个链接起的一个别名该参数将匹配容器name并建立连接
1
$ docker run -d -P --name web --link db training/webapp python app.py
使用
docker inspect
定位1
2$ docker inspect -f "{{ .HostConfig.Links }}" web
[/db:/web/db]一个创建在源容器与目标容器之间的连接允许源容器提供信息给目标容器。目标容器web可以获取源容器db提供的信息,为了实现这个功能,docker在这两个容器之间创建一个稳定的通道,并且不会暴露任何端口
环境变量
当link容器时,docker会创建许多环境变量,Docker会自动创建环境变量到目标容器中去
也会通过docker暴露源容器的所有环境变量,这些变量来自:
- Dockerfile中ENV命令定义的环境变量
- 在启动源容器时使用
-e
--env
--env-file
参数附加的环境变量
这些环境变量使程序从相关的目标容器中发现源容器
Docker为每一个在–link参数中的容器设置了一个_NAME环境变量。
例如:一个web容器通过
--link db : webdb
连接db容器,将会在web容器中创建一个WEBDB_NAME=/web/webdb环境变量Docker为源容器暴露的端口限定了一组环境变量,每一个环境变量具有唯一前缀形式
1
<name>_PORT_<port>_<protocol>
前缀的构成:
- 是–link : 后面的参数(webdb)
- 就是暴露的端口号
- TCP / UDP
Docker 利用这前缀格式定义了三个不同的环境变量
prefix_ADDR 变量包含了来自URL的IP地址,
1
WEBDB_PORT_5432_TCP_ADDR=172.17.0.82.
prefix_PORT 变量仅包含了URL的端口号
1
WEBDB_PORT_5432_TCP_PORT=5432.
prefix_PROTO 参数包含URL的传输协议
1
WEBDB_PORT_5432_TCP_PROTO=tcp.
如果容器暴露多个端口,Docker将会为每个端口创建三个环境变量
另外,Docker也要创建一个叫 _PORT的环境变量。这个变量包含源容器URL首次暴露的IP和端口,该端口的“首次”定义为最低级数字的端口
最后,Docker会把源容器中的环境变量暴露给目标容器作为环境变量。并且Docker会在目标容器为每个变量创建一个ENV变量。这个变量的值被设置为启动源程序Docker所用到的值
你可以使用env命令列出具体的容器环境变量
1
2
3
4
5
6
7
8
9$ docker run --rm --name web2 --link db:db training/webapp env
. . .
DB_NAME=/web2/db
DB_PORT=tcp://172.17.0.5:5432
DB_PORT_5432_TCP=tcp://172.17.0.5:5432
DB_PORT_5432_TCP_PROTO=tcp
DB_PORT_5432_TCP_PORT=5432
DB_PORT_5432_TCP_ADDR=172.17.0.5
. . .你可以看到Docker利用许多有关源容器的信息创建了一些列的环境变量。每一环境变量都会带有指点定义的别名,DB前缀。如果别名是db1,那么变量前缀也会变成DB1。利用这些环境变量来配置应用用在db容器上连接数据库。这样的连接方式稳定安全私有化,只有已获得连接的web容器才会有对db容器的访问权限
一些注意事项
与修改
/etc/hosts
文件不同,在环境变量中存储的IP地址信息不会随着容器的重启而更新,建议利用hosts文件来解决连接容器的IP地址问题这些环境变量只是为容器的第一个process设置,某些daemon后台服务,例如sshd,只有当产生连接需求时才会设置
更新
/etc/hosts
文件处理环境变量,docker在源文件中追加了host信息,这里向web容器追加
1
2
3
4
5$ docker run -t -i --rm --link db:webdb training/webapp /bin/bash
root@aed84ee21bde:/opt/webapp# cat /etc/hosts
172.17.0.7 aed84ee21bde
. . .
172.17.0.5 webdb 6e5cdeb2d300 db我们可以在容器中使用ping命令测试链接
1
2
3
4
5
6root@aed84ee21bde:/opt/webapp# apt-get install -yqq inetutils-ping
root@aed84ee21bde:/opt/webapp# ping webdb
PING webdb (172.17.0.5): 48 data bytes
56 bytes from 172.17.0.5: icmp_seq=0 ttl=64 time=0.267 ms
56 bytes from 172.17.0.5: icmp_seq=1 ttl=64 time=0.250 ms
56 bytes from 172.17.0.5: icmp_seq=2 ttl=64 time=0.256 ms如果你重启源容器,连接依然存在
1
2
3
4
5
6
7$ docker restart db
db
$ docker run -t -i --rm --link db:db training/webapp /bin/bash
root@aed84ee21bde:/opt/webapp# cat /etc/hosts
172.17.0.7 aed84ee21bde
. . .
172.17.0.9 db
其它Docker操作
日志相关
打印运行日志
1
2docker logs -f app
# -f 参数可以查看容器标准输出
仓库服务相关
登入登出
1
2
3# 输入docker id 和密码
docker login
docker logout如果有自己的仓库
1
2docker login localhost:8080
docker logout localhost:8080
Docker基本指令及用法
环境信息相关
- info
- version
系统运维相关
- attach
- build
- commit
- cp
- diff
- export
- images
- import
日志信息相关
仓库服务相关
搭建私有仓库
备份镜像,方便使用
如何搭建私有仓库
下载仓库镜像
1
docker pull registry
配置私有仓库
/etc/docker/daemon.json
1
2
3{
"insecure-registries": ["192.168.10.6:5000"]
}重启docker
1
systemctl restart docker
创建registry容器(关联私有仓库的配置)
1
docker run -d -p 5000:5000 --name registry registry:latest
备份镜像名字
1
docker tag ubuntu:latest 192.168.10.6:5000/ubuntu-copy
推送镜像到私有仓库
1
docker push 192.168.10.6:5000/ubuntu-copy
拉取私有仓库镜像
1
docker pull 192.168.10.6:5000/ubuntu-copy