docker技术笔记

1. 开始

1.1 认识

  • Docker推荐单容器单进程的实现方式

1.2 系统命令

1
docker info #查看docker状态信息

2. 容器

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
docker run -i -t ubuntu /bin/bash #使用镜像ubuntu运行随机名容器,并在运行时执行/bin/bash命令
docker run --name first_test -i -t ubuntu /bin/bash
docker run --name second_test -i -t unbuntu /bin/sh -c "while true;do echo hello world;sleep 1;done"
docker run --name daemon_test -i -t -d ubuntu /bin/bash #-d detached
docker run --restart=always --name daemon_test -d ubuntu /bin/bash
docker run -d -p 80:80 --name nginx_container nginx -g "daemon off;"
docker run -d -p 127.0.0.1:80:80 --name another_nginx_container nginx -g "deamon off;"
docker run -d -v /home/website:/opt/website #创建挂载点/opt/website 并将构建主机的/home/website挂载至该点
docker run -p 3467 --add-host=docker:10.0.0.1 --name website unbuntu 向hosts文件中添加记录 10.0.0.1 docker
docker run --rm --volumes-from blog -v $(pwd):/backup ubuntu tar cvf /backup/blog.tar /var/www/html #--rm 一次性容器,运行完自动删除 --volumes-from blog 共享blog容器所有的挂载点
docker ps
docker ps -a
docker ps -n x -q #查看最后x个容器的id
docker attach
docker logs first_test
docker logs --tail 10 -f first_test
docker top first_test
docker top first_test
docker stats
docker exec -d lvyan3 touch /etc/new_file
docker exec -t -i lvyan3 /bin/bash
docker exec -u username <containerid> pwd
docker start lvyan3 #统计信息
docker stop lvyan3
docker stop $(docker ps -q) #停止所有运行的容器
docker rm $(docker ps -a -q) #删除所有未运行容器
docker rm -f $(docker ps -a -q) #删除所有容器
docker rm lvyan3
docker rm -f lvyan3
docker inspect lvyan3
docker inspect --format='{{ .State.Status}}' lvyan3 lvyanpiaoliang
docker inspect ubuntu:latest 可查看镜像的Cmd等常见选项
docker cp <container_id>:/path/to/container_file /path/to/host_file

3. 镜像

3.1 常用命令

1
2
3
4
5
6
7
8
docker pull ubuntu:12.04 #拉取镜像
docker images #查看本地所有镜像
docker images ubuntu #查看名为ubuntu的镜像
docker search keyword #搜索关键字镜像
docker rmi [-f] image_name #删除镜像 -f
docker images prune #删除所有未使用的镜像
docker save --output ubuntu_1404.tar ubuntu:14.04 #导出镜像ubuntu:14.04到ubuntu_1404.tar
docker load --input ubuntu_1404.tar #从ubuntu_1404.tar导入到docker

3.2 镜像构建

从容器提交镜像 (不推荐)

docker commit

从dockerfile生成镜像

docker build

通过dockerfile构建镜像。构建时有构建缓存,如果构建内容不变会直接使用缓存构建,大大加快构架速度。构建时如果出问题,可直接进入构建时创建的临时容器进行debug

1
2
3
4
5
6
docker build -t "sheng9632/nginx:v2" .
docker build --no-cache -t "sheng9632/nginx:v3" .
docker build -t "sheng9632/fedora:20" -f fedorafile .
docker commit container_id sheng9632/apache2
docker commit -m "a new custom image" -a "sheng9632" container_id sheng9632/apache2:webserver

3.3 Dockerfile

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
66
67
68
69
EXPOSE 描述服务可用的端口,但并不创建和宿主机之间的映射
RUN 指定镜像被构建时要运行的命令
RUN ["apt-get","install","-y","nginx"]
CMD 指定容器启动时要执行的命令。
例1
CMD ["/bin/bash","-l"]
* 注意docker run 后的命令会覆盖CMD 例如 docker run -d sheng9632/nginx nginx -g "deamon off;" CMD命令被nginx...覆盖
* 多条CMD只执行最后一条
ENTERPOINT 指定的命令不会被覆盖,docker run后接的命令作为参数传递给ENTERPOINT指定的命令
例1
ENTERPOINT ["/usr/sbin/nginx"]
CMD ["-h"]
docker run -d image_name -g "deamon off;" 启动容器时会执行/usr/sbin/nginx -g "deamon off;"
docker run -d image_name 容器启动时会/usr/sbin/nginx -h
WORKDIR 设定工作目录
WORKDIR /home
RUN mkdir hello
WORKDIR /home/hello
RUN touch world
ENV 设定容器环境变量
ENV TEST=1 TEST2=2
LABEL
ADD 将构建环境下的文件和目录复制到镜像中
ADD soft.inc /opt/application/soft.inc
ADD http://wordpress.org/latest.zip /root/wordpress.zip
ADD latest.tar.gz /var/www/wordpress
* ADD 和 COPY都是以构建目录为相对目录的,只可以复制构建目录下的内容
COPY 同ADD,但不会对源进行解压或者压缩
USER 指定镜像以什么用户去执行
USER nginx
USER uid:gid
USER user:group
VOLUME 创建挂载点
VOLUME ["/opt/project","/data"] 创建挂载点/opt/project和/data 与构建主机没有任何关系
ARG 参数
ARG build #Dockerfile中声明变量build
ARG webapp_user=user #Dockerfile中声明变量webapp_user,并有默认值user
例1
docker build --build-arg build=1234 -t sheng9632/nginx . #在docker build时使用
例2
在docker-compose.yaml中定义arg,然后在Dockerfile中声明变量并使用 #同时在docker build时使用,只不过是通过docker-compose中变量赋值并传入docker build
ONBUILD 基于此镜像的子镜像在构建时执行的命令
ONBUILD RUN echo 'hello world' > /home/hello_world
docker run时也可使用部分指令
docker run
-w /home/hello 设定工作目录 WORKDIR
-v /opt/project:/data .... 创建添加卷 VOLOME
例:
FROM nginx
MAINTAINER xiangke.sun sxk10812139@163.com
ENV TEST=1 TEST2=2
WORKDIR /home
RUN mkdir hello
WORKDIR /home/hello
RUN touch hello
ADD Dockerfile /homedoc
COPY Dockerfile2 /home
ENTRYPOINT ['/usr/sbin/nginx']
CMD ['-g','daemon off;']
LABEL version="1.0"
LABEL location="New York" type="Data Center"
ONBUILD RUN echo 'hello world' > /home/hello_world
ARG build
ARG webapp_user=user
#ENTRYPOINT ['/usr/sbin/nginx','-g','daemon off;']

路径问题

注意 ADD/COPY 的源路径都是相对于Dockfile所在目录的路径,建议使用ADD/COPY之前将相关文件复制或移动到Dockfile目录下

Dockfile所在目录为/project/docker
ADD /etc/file …中/etc/file的意思是/project/docker/etc/file

4. 网络配置

网络模式

自建网络
    驱动
        bridge      #网桥模式 默认模式。默认网桥无法自动发现,用户自建网络自动配有发现服务,内部容器可直接互联。
        overlay     #overlay网络模式

默认网络(为了向下兼容老的方式)
    网络名
        none
        host
        bridge      #docker run 如果不指定net的话,会将容器加入到默认bridge网络中

docker网络模式

命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
docker network ls #显示网络列表
docker network inspect app #查看网络
docker network create app #创建网络
docker network connect app container_name #将连接容器至网络
docker network disconnect app container_name #从网络断开容器
docker network create --d bridge --subnet 172.18.0.0/16 net_name
--d 驱动器
--subnet 子网
docker run
--net 指定网络。
--ip 指定静态ip,如果没有指明静态ip,则会自动分配
docker run --net mynet --ip 172.18.0.1 ubuntu

网络别名

docker run —net mynet —network-alias a1 ubuntu
这样容器间不需要关心各自具体ip,网络互连只需要知道别名即可。举个例子,假如容器重启,ip进行了重新分配,容器间也可以正常互连。

更优雅的方式是多个容器连接自定义bridge网络,自动支持容器名服务发现

自建网络并添加两个容器

1
2
3
4
5
6
7
8
9
10
docker network create --subnet 172.18.0.0/16 mynet
docker run --net mynet --network-alias c1 -tid --name container1 ubuntu
docker run --net mynet --network-alias c2 -tid --name container2 ubuntu
docker exec -ti c1 /bin/bash
root@4e015c60b43e:/# ping c2
PING c2 (172.18.0.3) 56(84) bytes of data.
64 bytes from c2.mynet (172.18.0.3): icmp_seq=1 ttl=64 time=0.109 ms
64 bytes from c2.mynet (172.18.0.3): icmp_seq=2 ttl=64 time=0.091 ms
64 bytes from c2.mynet (172.18.0.3): icmp_seq=3 ttl=64 time=0.091 ms

多个容器共享一个别名,类似于集群

多容器共享别名,有点类似集群负载均衡,请求会依次解析到不同的容器

5. 工具

5.1 supervisor 类UNIX操作系统的进程管理工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Supervisor是一个进程管理工具,用途就是有一个进程需要每时每刻不断的跑,但是这个进程又有可能由于各种原因中断。当进程中断的时候我们希望能自动启动它,此时我们就需要使用Supervisor
默认配置位置/etc/supervisor/conf.d/supervisord.conf
启动命令 /usr/bin/supervisord
例:
第一步 创建supervisord.conf配置文件
[supervisord]
nodaemon=true #表示supervisord不以daemon形式启动(supervior持续输出,所以docker中其他程序不需要设定为非daemon模式)
[program:nginx]
command=/usr/sbin/nginx #启动nginx
第二步 创建Dockerfile
FROM ubuntu:14.04
MAINTAINER xiangke.sun "sxk10812139@163.com"
RUN sed -i 's/http:\/\/archive\.ubuntu\.com\/ubuntu\//http:\/\/mirrors\.163\.com\/ubuntu\//g' /etc/apt/sources.list #更改apt源为163源
RUN apt-get update && apt-get install -y nginx supervisor #更新apt包信息 安装nginx和supervisor
COPY ./supervisord.conf /etc/supervisor/conf.d/supervisord.conf #复制supervisor配置到容器中
CMD ["/usr/bin/supervisord"] #添加默认启动命令
第三步 创建容器
docker run -tid -name mytest -p 80 image_id

6. 编排

6.1 docker-compose

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
https://docs.docker.com/compose/compose-file/
包含docker run、docker network create、docker volume create等内容
docker-compose up [-d] 使用当前目录下的docker-compose.yaml文件
docker-compose down
例:
#配置参考(https://docs.docker.com/compose/compose-file/)
docker-compose.yaml
version: '2'
services:
webnew:
image: nginx
command: nginx -g "daemon off;"
container_name: newnginx
ports:
- "8090:80"
depends_on: #redis会先被构建
- redis
redis:
image: redis
* yaml文件:后需要跟空格;缩进不能用\t,只能用空格

7. 实例

7.1.php与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
33
34
35
36
37
38
39
40
41
42
43
44
第一步 创建镜像
创建php
FROM ubuntu:14.04
MAINTAINER xiangke.sun "sxk10812139@163.com"
RUN sed -i 's/http:\/\/archive\.ubuntu\.com\/ubuntu\//http:\/\/mirrors\.163\.com\/ubuntu\//g' /etc/apt/sources.list
RUN apt-get install python-software-properties #添加add-apt-repository命令
RUN export LANG=C.UTF-8
RUN add-apt-repository ppa:ondrej/php #添加php源
RUN apt-get -y update
RUN apt-get install php5.6-cli php5.6-cli php5.6-fpm php5.6-mysql
RUN apt-get -y install php5.6-fpm php5.6-mcrypt php5.6-mbs tring php5.6-curl php5.6-cli php5.6-mysql php5.6-gd php5.6-intl php5.6-xsl
RUN service php5.6-fpm start
创建nginx容器
FROM ubuntu:14.04
MAINTAINER xiangke.sun "sxk10812139@163.com"
RUN sed -i 's/http:\/\/archive\.ubuntu\.com\/ubuntu\//http:\/\/mirrors\.163\.com\/ubuntu\//g' /etc/apt/sources.list
RUN apt-get -y update && apt-get install nginx #安装nginx
RUN nginx -g "daemon off;"
docker build -t "sheng9632/nginx:new" .
docker build -t "sheng9632/php:5.6" .
第二步 启用容器并加入网络
docker network create --subnet 172.20.0.0/16 work #创建自定义桥接网络 模式驱动为桥接
docker run -tid --name php sheng9632/php:5.6 --net work php #创建容器php并加入work网络
docker run -tid --name nginx -p 8080:80 sheng9632/nginx:new nginx #创建容器nginx并加入work网络
第三步 进入php容器,调整php-fpm监听地址
docker exec -ti php /bin/bash #进入容器php
vi /etc/php/5.6/fpm/pool.d/www.conf #调整php-fpm默认进程监听为所有9000端口的ipv4笛子
listen=9000 (监听所有ipv4地址9000端口的访问)
第四步 启用nginx调整php内容处理地址
docker exec -ti nginx /bin/bash #进入容器nginx
vi /etc/nginx/sites-available/default #调整php处理地址为php容器的php-fpm进程监听端口
location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass php:9000;
fastcgi_index index.php;
include fastcgi_params;
}
第四步 测试
docker ps #宿主机上查询nginx容器可访问端口,尝试访问

8.调试

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
在build镜像时每一层会生成一个镜像id
通过docker run -ti 镜像id 可以进入该层进行debug
# 获取容器日志
docker logs <containerid>
# 实时查看stdout
docker attach <containerid>
# 查看容器进程
docker top <containerid>
# 查看容器统计信息
docker stats <containerid>
# 查看容器详细信息
docker inspect <containerid>
State —— 容器的当先状态
LogPath —— history(stdout) file 的路径
Config.Env —— 环境变量
NetworkSettings.Ports —— 端口的映射关系
# 查看image layer
docker history <imageid>
可以看到各层创建的指令,大小和哈希。可以用来检查这个image是否符合你的预期

统计信息
20180915153694332034228.png

9.FAQ

1.nginx连接传输速度太慢

1
2
使用国内源
RUN sed -i 's/http:\/\/archive\.ubuntu\.com\/ubuntu\//http:\/\/mirrors\.163\.com\/ubuntu\//g' /etc/apt/sources.list

2.Docker为什么刚运行就退出了?

这个是docker的机制问题,Docker容器后台运行,就必须有一个前台进程.容器运行的命令如果不是那些一直挂起的命令(比如运行top,tail)则会自动退出。

比如你的web容器,我们以nginx和fpm为例,正常情况下,我们配置启动服务只需要启动响应的service即可,例如service nginx start && service php5-fpm start,但是这样做nginx和fpm均为后台进程模式运行,就导致docker前台没有运行的应用,这样的容器后台启动后会立即自杀,因为他觉得他没事可做了.

解决方法

i.最佳的解决方案是,将你要运行的程序以前台进程的形式运行,当然,如果你的容器需要同时启动多个进程,
那么也只需要,或者说只能将其中一个挂起到前台即可.
比如上面所说的web容器,我们只需要将启动指令修改为:service php5-fpm start && nginx -g “daemon off;”
这样,fpm会在容器中以后台进程的方式运行,而nginx则挂起进程至前台运行,这样,就可以保持容器不会认为没事可做而自杀了.

ii.对于有一些你可能不知道怎么前台运行的程序,提供一个投机方案,你只需要在你启动的命令之后,
添加类似于 tail top 这种可以前台运行的程序,这里特别推荐 tail ,然后持续输出你的log文件.
还是以上文的web容器为例,我们还可以写成:service nginx start && service php5-fpm start && tail -f /var/log/nginx/error.log
docker run —name website -d -v /home/docker/sample/website:var/www/html/website sheng9632/nginx:1809 nginx

参考http://blog.csdn.net/meegomeego/article/details/50707532

3.172.18.0.0/16是什么意思

子网掩码用来标识ip中的网络位和主机位的32位数字,/16指子网掩码为的前16位为1. 172.18.0.0/16指网络号为172.18.0.0的网络,主机号由0.1至255.254都包括在该子网内

4.重启服务后,容器的网络ip变化,如何解决

1.自建网络支持发现服务,可以使用别名或容器名访问其他机器
2.在生成容器时指定指定静态Ip

5.为什么ENTRYPOINT [‘/usr/sbin/nginx’,’-g’,’daemon off;’] 在执行docker run xxxx后不生效

答: dockerfile中请使用双引号””

6.为什么挂载添加卷volume时提示权限不足

答: 以用户A运行docker,如果要挂载添加卷,A需要对添加卷有权限,并且在添加卷中创建的文件权限全部为A

7.如何分离php和web服务器

答: nginx解释php地址改为网络地址 php容器名:php进程池监听端口号

8.如何detach一个会话(不停止容器脱离会话)

如果容器是以-it运行的,那么可以使用ctrl+p,ctrl+q来detach(不会终端容器运行)
例1
    docker run --name test -tid nginx nginx -g "daemon off;"
    docker attach test
    ctrl+p ctrl+q  
例2 
    docker run --name test -ti nginx nginx -g "daemon off;"
    ctrl+p ctrl+q

9.像账号密码等如何传入容器

在env中设定,然后在容器中运行脚本文件启动服务,这时候从env中获取账号密码等

10.docker容器如何访问宿主机服务(比如连接宿主机上的mysql数据库)

目前有两种方式

  • 如果网络是网桥驱动(主机作为网关存在)
    • export DOCKER_HOST_IP=$(ip route|awk ‘/docker0/ {print $NF}’) 或者是子网确定的情况下,主机Ip直接取网关地址 例 子网172.18.0.0/16 网关就是172.18.0.1,使用DOCKER_HOST_IP来与主机连接
  • 如果网络使用host模式
    • 直接127.0.0.1上访问mysql即可

如果是mac环境,使用docker.for.mac.host.internal访问宿主机

删除所有未使用的镜像

1
docker rmi $(docker images -f "dangling=true" -q)

同主机容器间通信

同一bridge下

容器内某些命令无法执行

1
2
$ strace -p xx
xxxxxxxxOperation not permitted

默认情况下,docker以一种非特权的模式运行,该模式下容器屏蔽了很多linux能力(capabilities),导致我们无法在容器内部直接使用某些命令,如果你有这方面的需求就需要为容器增加这方面的能力
如果是测试或开发环境等,可以采用傻瓜式配置--privileged开放全部特权

1
docker run --privileged ...

如果不想开放所有权限,可以通过--cap-add--cap-drop精细化配置增加或要删除的能力

1
2
3
4
5
docker run --cap-add=SYS_PTRACE ...
docker run --cap-add=ALL --cap-drop=SYS_PTRACE ...
docker run --cap-add=SYS_PTRACE --security-opt seccomp=unconfined
[Runtime privilege and Linux capabilities](https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities)

>

  • capabilities
    capabilities是linux内核的一项特性,它将root用户的权限细分为不同的领域,可以分别对其启用或关闭。实际进行特权操作时,如果euid(系统决定用户对系统资源的访问权限,通常情况下等于ruid)不是root,便会检查是否具有该权限操作锁对应的capabilities,并以此为依据,决定是否可以执行特权操作。
  • seccomp
    seccomp是secure computing的缩写,是linux内核2.6.23版本后引入的一种简洁的沙箱机制。secommp用以控制一个用户态程序可以执行的系统调用,相当于一个系统调用白名单。

参考资料

理解docker容器的运行用户

docker与宿主机共享同一个内核,又用户管理是由内核负责的,可得容器内外相同uid代表相同的用户;
由于用户名不是由内核管理的,所以容器内外相同uid的用户名很可能不同。
容器启动时可以手动指明uid(-u xxx),如果未指明,则使用uid为0(root)用户来运行容器进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 当前目录root可写
$ ls -l /root|grep docker
drwxr-xr-x 2 root root 4096 Apr 17 03:00 docker-test
# 使用默认用户(默认为uid0 root)创建容器
$ docker run -ti --rm --name s1 -v $(pwd):/tmp alpine
/ $ touch container_write
/ $ test
# 宿主机看到文件创建人uid为0
$ ls -l
-rw-r--r-- 1 root root 0 Apr 17 03:00 container_write
# 使用uid1000用户来创建容器
$ docker run -ti -u 1000 --rm --name s1 -v $(pwd):/tmp alpine
/ $ touch container_write_1000
# 1000用户在当前目录无写权限,故写失败
touch: container_write_1000: Permission denied
/ exit
* -u 可以是用户名或UID