ITPub博客

首页 > 数据库 > 数据库开发技术 > 如何优雅的使用docker工具去管理容器

如何优雅的使用docker工具去管理容器

原创 数据库开发技术 作者:云原生运维技术 时间:2017-01-20 20:57:50 0 删除 编辑

       优雅的使用docker内置的一些工具,并组合使用会使整个docker容器管理操作过程变得非常简洁高效,本篇文章总结了自己遇到的一些比较好的技巧,和大家分享分享,欢迎吐槽😄!

创建容器时传入环境变量

在实际应用场景中,不论是从安全还是可配置方面去考虑,很多参数是比较适合用环境变量加载进去的,比如数据库的连接信息,时区,还有字体支持等等,在创建容器的时候其实都可以使用-e 指定key/value进行传递环境变量进去。

sh-4.2# docker run -itd --name test-env -e TZ='Asia/Shanghai' 172.25.46.9:5001/centos6.8-jdjr-test-app 
ee20b44301e27c16eae63dab243d293054178dd5f819c23d44bd9e534208bb42
sh-4.2# docker exec -it test-env date
2017年 01月 17日 星期二 10:35:17 CST
sh-4.2# date
Tue Jan 17 10:35:21 CST 2017
可以看到加了时区环境变量的容器已经和宿主机在同一个时区(CST),并且时间和宿主机基本同步

sh-4.2# docker run -itd --name test  172.25.46.9:5001/centos6.8-jdjr-test-app
d6a02874b999ff4eea79e3b302148b42043af01c89a5d31e5d858e0806f9077a
sh-4.2# docker exec -it test date
2017年 01月 20日 星期五 01:43:48 Asia
默认没有加时区环境变量的容器还是Asia

调整宿主机和容器的时间差异

 首先我们需要弄清几个概念:在类unix系统中有硬件时钟与系统时钟,硬件时钟是指主机板上的时钟设备,也就是通常可在BIOS画面设定的时钟,系统时钟则是指kernel中的时钟。unix以及linux系统时间是从格林威治时间到当前的秒数,即1970年1月1日凌晨零点零分零秒到当前的时间,全球都一样,这是绝对值;而时区则是由于地理位置差异、行政区划导致各地显示时间的差异,为了克服时间上的混乱,规定将全球划分为24个时区,我们国家属于东八区标识为CST。

 因此,对于 Docker 容器而言,根本不存在宿主和容器的时间差异问题,因为他们使用的是同一个内核、同一个时钟,二者完全一样,所以根本不存在同步问题。一般来说这个问题是由时区导致的,可以使用date命令查看下容器当前的时间时区是啥。UTC(通用协调时)表示使用的是国际标准0时区,UTC与格林尼治平均时(GMT, Greenwich Mean Time)一样,都与英国伦敦的本地时相同。CST表示中国标准时间时区一般是中国上海”Aisa/Shanghai”,也就是说UTC和CST相差了8个小时。

解决办法:
创建容器的时候,使用-e 将时区信息传入到容器内部。
sh-4.2# docker run -itd --name test-env -e TZ='Asia/Shanghai' images

指定容器的rootfs的大小

 在使用docker的过程中,会发现cpu和memory可以很随意的动态调整,但是默认的rootfs却是不能随意调整的,默认是10g大小,当然如果对于数据有需求,可以通过挂载voulme进行扩展存储。如果用户执意想要调整rootfs的大小,在docker1.12版本默认提供了两种方式:在启动docker 的时候加载参数--storage-opt dm.basesize=40G用来调整默认容器的rootfs大小;在创建容器的时候使用参数--storage-opt size=70G来设置改容器的rootfs大小。福利:在docker最近发布的1.13版本中,支持了磁盘的配额,不过还未测试

sh-4.2# docker run -itd --name volume-test --storage-opt size=70G 172.25.46.9:5001/centos6.8-jdjr-test-app
18d47e69802aa84df00182885b256c50ebc56e15d8e6990fc1e187ffe254171e

sh-4.2# docker exec -it volume-test df -H | grep rootfs
rootfs                 76G  1.5G   74G   2% /
sh-4.2# docker exec -it test-env df -H | grep rootfs
rootfs                 11G  1.5G  9.3G  14% /

快速管理容器和镜像

  在docker中删除容器需要指定容器名或者容器id,但是在容器比较多,并且状态不一的情况下删除容器还是需要走下心的。不过好处是docker ps默认提供了很多好用的功能,可以很方便地管理容器(创建容器的时候如果加上label后更方便哦)。

原理:先用docker ps -a -q 输出所有容器的container id(-f 表示过滤参数或者输出格式),然后作为docker rm 的参数进行批量删除
输出所有容器的name:
sh-4.2# docker ps --format='{{.Names}}'
test-env
test-args
test-run
输出所有容器名包含test的容器,并打印容器名
sh-4.2# docker ps -f name=test --format='{{.Names}}'
test-env
test-args
test-run

删除所有容器:
sh-4.2# docker rm -f -v $(docker ps -a -q)
删除/启动所有退出的容器:
sh-4.2# docker rm/start $(docker ps -qf status=exited)
删除所有镜像:
sh-4.2# docker rmi $(docker images -q)

容器label的使用

 在实际运维过程中,大量的容器可能会一些运维上的挑战,通过使用label,可以很好的将容器分类。label贯穿于docker的整个过程。 这个label可以作为你区分业务,区分模板各种区分容器的标识,通过标识,可以将容器更好的进行分组。

sh-4.2# docker run -itd --name volume-test --storage-opt size=70G --label zone=test 172.25.46.9:5001/centos6.8-jdjr-test-app
c3772397e58e663095c2c0fd8d688b3d41b494097999ec2b6d6b7c509d23a138
创建容器的时候定义一个label,表示该容器在test这个区域
使用定义的label进行快速检索容器,并进行下一步操作(比如删除啦,更新啦)
sh-4.2# docker ps -qf label=zone=test
c3772397e58e
sh-4.2# docker ps -f label=zone=test --format='{{.Names}}'
volume-test

快速查看容器的相关配置信息

查看容器的devicemapper设备:
sh-4.2# docker inspect -f '{{.GraphDriver.Data.DeviceName}}' nginx
docker-8:1-67411759-7c9d6d3327b02659c81bcb70bf6a4c7a45df6a589af2a2d42a387dc0e90d4913
查看容器的PID:
sh-4.2# docker inspect -f '{{.State.Pid}}' nginx
27521
查看容器name:
sh-4.2# docker inspect -f '{{.Name}}' nginx
/nginx

使用alias来预定义常用的命令

 docker管理命令经常需要指定各种参数,通过linux的alias命令将默认的参数预定义起来,可以很方便的进行管理容器。

sh-4.2# alias dockerrm='docker rm -f -v'
sh-4.2# alias dockerexec='docker exec -it'
sh-4.2# alias dockerrmimage='docker rmi'

sh-4.2# dockerrm volume-test
volume-test

sh-4.2# dockerexec volume-test ls
bin   dev  export  lib    media  opt   root  selinux  sys  usr
boot  etc  home    lib64  mnt    proc  sbin  srv      tmp  var
sh-4.2# dockerexec volume-test bash
bash-4.1#

使容器随着docker daemon的启动一同启动

docker run 的时候加参数--restart=always

如何动态修改容器的内存和cpu限制docker1.10之后才支持的动态调整

sh-4.2# dockerexec test-env cat /sys/fs/cgroup/memory/memory.limit_in_bytes
9223372036854775807
sh-4.2# cat /sys/fs/cgroup/memory/memory.limit_in_bytes
9223372036854775807
可以看到,默认没有给容器限制内存,它会共享宿主机的所有内存
动态调整内存为2014M:
sh-4.2# docker update -m 2014M test-env
test-env
sh-4.2# dockerexec test-env cat /sys/fs/cgroup/memory/memory.limit_in_bytes
2111832064

docker容器中真实用户的隔离

 注意:在使用docker的过程中,由于每个容器都是独立的namespace,而且每个容器的信息其实都是由宿主机去统一管控的,因而在容器启动对应的进程的时候,在宿主机是能够看到某个容器启动进程的相关信息的。由此而引发的另外一个问题就是用户uid的问题。默认docker容器内部的用户会继承宿主机的用户id,也就是说容器外部有一个uid为500的用户test,容器内部有一个uid为500的用户admin,容器内部运行的程序如果在宿主机上查看的时候会发现程序的启动用户会是外部宿主机的test用户。 这是因为默认情况下容器的 user namespace 并未开启,所以容器内的用户和宿主用户共享 uid 空间。容器内的 uid 为 0 的 root,就被系统视为 uid=0 的宿主 root,因此磁盘读写时,具有宿主 root 同等读写权限。 
假设我的images中当时创建了admin用户,且uid为500,目前启动两个容器,分别为app1,app2:

sh-4.2# docker ps 
CONTAINER ID        IMAGE                                    COMMAND                  CREATED             STATUS              PORTS               NAMES
ce5ffce5e758       sandbox-jdk7-tomcat6    "/bin/sh -c '/usr/sbi"   13 minutes ago      Up 13 minutes                           app1
28db02f62694       jdk6-tomcat6-sshd-new   "/bin/sh -c '/usr/sbi"   7 days ago          Up 2 days                               app2


sh-4.2# docker exec -it app1 id admin
uid=500(admin) gid=500(admin) groups=500(admin)
sh-4.2# docker exec -it app2 id admin
uid=500(admin) gid=500(admin) groups=500(admin)

sh-4.2# grep 500 /etc/group
sh-4.2#

注意:宿主机目前是没有500这个uid对应的用户的

分别在两个容器中使用admin用户去启动两个进程,并使用某一个容器的root用户去启动一个进程。在docker宿主机上看到的其实是这样的:

sh-4.2# ps -ef | grep ping 
500      13763     1  0 10:42 ?        00:00:00 ping    由app1的admin启动
500      14795     1  0 10:45 ?        00:00:00 ping www.qq.com       由app2的admin启动
root     16100     1  0 10:49 ?        00:00:00 ping       由app2的root启动
root     24271 18645  0 11:17 pts/0    00:00:00 grep ping

        在宿主机上看到相关的进程如上显示,会发现baidu和qq的ping进程都是由uid为500的用户启动的,PPID为1,但其实这两个进程是完全隔离的在不同的namespace的,而且两个用户虽然看起来一样,但实际是不影响的。 由app2的root用户启动的jd的ping进程可以发现是由root启动,且ppid为1.因为宿主机上本身是有root用户,因此容器内部的进程信息(用root启动的ping)映射出来之后就会映射到宿主机的root用户信息中,但实际上进程还是隔离的。这里在视觉上还是需要注意区分一下。 

总结:

在docker使用过程中,还是建议在容器内部使用一个宿主机不存在的用户(uid)去启动容器内部的应用进程(建议在做标准化的时候,在images内部就打上一个用于启动应用进程的用户,以便和宿主机的用户进行区分),否则用户可能看到的容器内部使用user1启动的程序,而在宿主机上看,会发现该程序可能是user2去启动的。在之后的问题梳理以及状态监测中可能发生混淆。

问题避免:
开启user namespace:
启动docker的时候加参数--userns-remap=default
https://docs.docker.com/engine/reference/commandline/dockerd/#/daemon-user-namespace-options

在docker container和物理机中双向拷贝文件

容器内部文件拷贝到宿主机:
sh-4.2# docker cp jupyter-70002111:/home/70002111/教程-研究功能介绍.ipynb .
sh-4.2# ls
Dockerfile  教程-研究功能介绍.ipynb
宿主机文件拷贝到容器:
sh-4.2# docker cp Dockerfile jupyter-70002111:/home/70002111/
sh-4.2# docker exec -it jupyter-70002188 ls
Dockerfile

向容器内部程序发送signal

注意:在给容器进程发送SIGTERM信号时只会发给主进程,也就是容器内 PID 为 1 的进程。至于说主进程启动的那些子进程,完全看主进程是否愿意转发SIGTERM 给子进程了。所以那些把 Docker当做虚拟机用的,主进程跑了个bash,然后exec 进去启动程序的,或者来个&让程序跑后台的情况,应用进程必然无法收到SIGTERM。 
还有一种可能是在Dockerfile中的CMD那行用的是 shell 格式写的命令,而不是 exec 格式。在镜像中使用CMD启动的容器会加一个 sh -c 来去执行,因此使用 shell 格式写 CMD 的时候,PID 为 1 的进程是 sh,而它不转发信号,所以主程序收不到。

所以在写CMD哪行命令的时候,最好按照exec格式去写。


欢迎吐槽交流!



原文作者:云原生运维技术;如有侵权,请联系管理员删除。

下一篇: 没有了~
请登录后发表评论 登录
全部评论

注册时间:2017-01-18

  • 博文量
    53
  • 访问量
    20336