课程名称:Docker 系统性入门+进阶实践(最新版)
课程章节:第2章 容器快速上手
课程讲师: 麦兜搞IT
课程目标:掌握 容器相关知识
课程内容:
第2章 容器快速上手
认识一下docker命令行
镜像和容器
创建我们第一个容器
命令行小技巧之批量操作
容器的attached和detached模式
容器的交互式模式
windows是如何运行docker engine的
容器和虚拟机
创建容器时背后到底发生了什么
理解容器的本质最简单的方式就是类比。
进程是程序的运行实体;
容器是镜像的运行实体。
镜像和程序的角色是一样的,只不过镜像要比程序更加的丰富。程序只是按简单的格式存储在文件系统中,而镜像是按层,以联合文件系统的方式存储。
容器和进程的角色也是类似的,只不过容器相比于普通进程多了更多地附加属性。
既然容器也是进程,那么它一定也有进程号,那么如何将容器映射到操作系统的进程呢?我们这里还是以 Docker 容器为例。通过 docker top 命令可以看到容器的进程号。下面举个例子。
从上图我们可以看进程的信息包括:
参数 | 含义 |
---|---|
USER: | 进程的启动用户; |
PID: | 进程号,每个进程都会被分配一个 PID,是一种系统资源,并且每个系统中的进程号个数是有限的; |
%CPU: | CPU 使用率; |
%MEM: | 内存使用率; |
理解容器的本质最简单的方式就是类比。
进程是程序的运行实体;
容器是镜像的运行实体。
镜像和程序的角色是一样的,只不过镜像要比程序更加的丰富。程序只是按简单的格式存储在文件系统中,而镜像是按层,以联合文件系统的方式存储。
容器和进程的角色也是类似的,只不过容器相比于普通进程多了更多地附加属性。
既然容器也是进程,那么它一定也有进程号,那么如何将容器映射到操作系统的进程呢?我们这里还是以 Docker 容器为例。通过 docker top 命令可以看到容器的进程号。下面举个例子。
[root@dockerdemo ~]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES d3973eb73bec http-server:v1 "/http-server" 35 hours ago Up 35 hours 0.0.0.0:8091->8091/tcp clever_nobel bf90054c3017 google/cadvisor:latest "/usr/bin/cadvisor -…" 12 days ago Up 12 days 0.0.0.0:8081->8080/tcp cadvisor 246cf9479cdf busybox "sh" 12 days ago Up 12 days ecstatic_shirley ff4f54614a02 busybox "sh" 12 days ago Up 12 days boring_meitner 9d72cb96129c busybox "sh" 13 days ago Up 13 days priceless_shannon [root@dockerdemo ~]# docker top d3973eb73bec UID PID PPID C STIME TTY TIME CMD root 25533 25514 0 Jun25 ? 00:00:00 /http-server [root@xxx ~]# ps aux | grep 25533 root 7008 0.0 0.0 112716 964 pts/0 S+ 20:26 0:00 grep --color=auto 25533 root 25533 0.0 0.0 707104 2564 ? Ssl Jun25 0:00 /http-server
我们的 http-server 容器对应的操作系统进程号就为 25533 号进程。为了更加直接的感受一下容器是一种进程,我们可以看一下 /proc/ 这个目录。在 Linux 中,每个进程的信息都可以通过目录 /proc 下面查找到,进程号会作为目录的名称。
[root@dockerdemo proc]# ls 1 10733 13860 18 2227 25514 3041 33 456 542 659 7922 8440 cpuinfo irq modules swaps 10 10773 14 1843 22270 25533 3042 3327 46 543 7 7923 8442 crypto kallsyms mounts sys 1006 1078 14011 19 22288 26 3043 3333 47 57 7274 7941 8443 devices kcore mtrr sysrq-trigger 10148 11 14180 2 22392 260 30526 34 4765 5716 7283 7994 8450 diskstats keys net sysvipc 10173 11247 1502 2068 23 26513 306 3475 4767 5718 75 8 8988 dma key-users pagetypeinfo timer_list 10338 11632 1503 2083 2312 27900 3074 35 49 59 7618 8043 9 driver kmsg partitions timer_stats 1035 12 1505 20890 24 28 3075 36 5163 6 7624 8062 9006 execdomains kpagecount sched_debug tty 10353 12461 15489 20892 24985 28390 30761 3600 517 60 7626 8122 acpi fb kpageflags schedstat uptime 1036 13 16 21 2500 289 31 37 529 61 7730 8205 buddyinfo filesystems loadavg scsi version 1038 1301 17 21249 2520 29 32 3706 531 611 7740 8252 bus fs locks self vmallocinfo 1039 13267 17030 2140 25247 294 32226 38 532 62 7774 8341 cgroups interrupts mdstat slabinfo vmstat 1046 1372 17165 22 2531 30 32676 39 5343 624 7806 8343 cmdline iomem meminfo softirqs zoneinfo 1051 1376 172 22264 25508 301 3277 4 5361 646 7814 8439 consoles ioports misc stat [root@emr-header-1 proc]# cd 25533 [root@emr-header-1 25533]# ls attr clear_refs cpuset fd limits mem net oom_score personality schedstat stack syscall wchan autogroup cmdline cwd fdinfo loginuid mountinfo ns oom_score_adj projid_map sessionid stat task auxv comm environ gid_map map_files mounts numa_maps pagemap root setgroups statm timers cgroup coredump_filter exe io maps mountstats oom_adj patch_state sched smaps status uid_map [root@xxx 25533]# ls -al ns total 0 dr-x--x--x 2 root root 0 Jun 25 09:40 . dr-xr-xr-x 9 root root 0 Jun 25 09:40 .. lrwxrwxrwx 1 root root 0 Jun 26 20:29 ipc -> ipc:[4026532462] lrwxrwxrwx 1 root root 0 Jun 26 20:29 mnt -> mnt:[4026532460] lrwxrwxrwx 1 root root 0 Jun 25 09:40 net -> net:[4026532524] lrwxrwxrwx 1 root root 0 Jun 26 20:29 pid -> pid:[4026532463] lrwxrwxrwx 1 root root 0 Jun 26 20:29 user -> user:[4026531837] lrwxrwxrwx 1 root root 0 Jun 26 20:29 uts -> uts:[4026532461] [root@xxx 25533]# cat cgroup 11:cpuset:/docker/d3973eb73bec5e62bf47710d8607a87ce27973c3dcd653b39eae41da25564d4d 10:hugetlb:/docker/d3973eb73bec5e62bf47710d8607a87ce27973c3dcd653b39eae41da25564d4d 9:perf_event:/docker/d3973eb73bec5e62bf47710d8607a87ce27973c3dcd653b39eae41da25564d4d 8:pids:/docker/d3973eb73bec5e62bf47710d8607a87ce27973c3dcd653b39eae41da25564d4d 7:freezer:/docker/d3973eb73bec5e62bf47710d8607a87ce27973c3dcd653b39eae41da25564d4d 6:memory:/docker/d3973eb73bec5e62bf47710d8607a87ce27973c3dcd653b39eae41da25564d4d 5:net_prio,net_cls:/docker/d3973eb73bec5e62bf47710d8607a87ce27973c3dcd653b39eae41da25564d4d 4:devices:/docker/d3973eb73bec5e62bf47710d8607a87ce27973c3dcd653b39eae41da25564d4d 3:blkio:/docker/d3973eb73bec5e62bf47710d8607a87ce27973c3dcd653b39eae41da25564d4d 2:cpuacct,cpu:/docker/d3973eb73bec5e62bf47710d8607a87ce27973c3dcd653b39eae41da25564d4d 1:name=systemd:/docker/d3973eb73bec5e62bf47710d8607a87ce27973c3dcd653b39eae41da25564d4d
上图显示的就是 http-server 这个容器作为操作系统的进程的一些基本信息,比如 ns 目录就对应 6 个不同的 namespace,而 cgroup 则对应 11 种不同的 cgroup。
在使用 -d 参数时,容器启动后会进入后台。 某些时候需要进入容器进行操作,有很多种方法,包括使用 docker attach 命令或 nsenter 工具等。
attach 命令
docker attach 是Docker自带的命令。下面示例如何使用该命令。
$ sudo docker run -idt ubuntu 243c32535da7d142fb0e6df616a3c3ada0b8ab417937c853a9e1c251f499f550 $ sudo docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 243c32535da7 ubuntu:latest "/bin/bash" 18 seconds ago Up 17 seconds nostalgic_hypatia $sudo docker attach nostalgic_hypatia root@243c32535da7:/#
但是使用 attach 命令有时候并不方便。当多个窗口同时 attach 到同一个容器的时候,所有窗口都会同步显示。当某个窗口因命令阻塞时,其他窗口也无法执行操作了。
nsenter 命令
安装
nsenter 工具在 util-linux 包2.23版本后包含。 如果系统中 util-linux 包没有该命令,可以按照下面的方法从源码安装。
$ cd /tmp; curl https://www.kernel.org/pub/linux/utils/util-linux/v2.24/util-linux-2.24.tar.gz | tar -zxf-; cd util-linux-2.24; $ ./configure --without-ncurses $ make nsenter && sudo cp nsenter /usr/local/bin
使用
nsenter 可以访问另一个进程的名字空间。nsenter 要正常工作需要有 root 权限。 很不幸,Ubuntu 14.04 仍然使用的是 util-linux 2.20。安装最新版本的 util-linux(2.24)版,请按照以下步骤:
$ wget https://www.kernel.org/pub/linux/utils/util-linux/v2.24/util-linux-2.24.tar.gz; tar xzvf util-linux-2.24.tar.gz $ cd util-linux-2.24 $ ./configure --without-ncurses && make nsenter $ sudo cp nsenter /usr/local/bin
为了连接到容器,你还需要找到容器的第一个进程的 PID,可以通过下面的命令获取。
PID=$(docker inspect --format "{{ .State.Pid }}" <container>)
通过这个 PID,就可以连接到这个容器:
$ nsenter --target $PID --mount --uts --ipc --net --pid
下面给出一个完整的例子。
$ sudo docker run -idt ubuntu 243c32535da7d142fb0e6df616a3c3ada0b8ab417937c853a9e1c251f499f550 $ sudo docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 243c32535da7 ubuntu:latest "/bin/bash" 18 seconds ago Up 17 seconds nostalgic_hypatia $ PID=$(docker-pid 243c32535da7) 10981 $ sudo nsenter --target 10981 --mount --uts --ipc --net --pid root@243c32535da7:/#
更简单的,建议大家下载 .bashrc_docker,并将内容放到 .bashrc 中。
$ wget -P ~ https://github.com/yeasy/docker_practice/raw/master/_local/.bashrc_docker; $ echo "[ -f ~/.bashrc_docker ] && . ~/.bashrc_docker" >> ~/.bashrc; source ~/.bashrc
这个文件中定义了很多方便使用 Docker 的命令,例如 docker-pid 可以获取某个容器的 PID;而 docker-enter 可以进入容器或直接在容器内执行命令。
$ echo $(docker-pid <container>) $ docker-enter <container> ls
启动Docker容器
启动容器有两种方式,一种是基于镜像新建一个容器并启动,另外一个是将在终止状态(stopped)的容器重新启动。
因为 Docker 的容器实在太轻量级了,很多时候用户都是随时删除和新创建容器。
所需要的命令主要为 docker run。
例如,下面的命令输出一个 “Hello World”,之后终止容器。
$ sudo docker run ubuntu:14.04 /bin/echo 'Hello world' Hello world
这跟在本地直接执行 /bin/echo ‘hello world’ 几乎感觉不出任何区别。
下面的命令则启动一个 bash 终端,允许用户进行交互。
$ sudo docker run -t -i ubuntu:14.04 /bin/bash root@af8bae53bdd3:/#
其中,-t 选项让Docker分配一个伪终端(pseudo-tty)并绑定到容器的标准输入上, -i 则让容器的标准输入保持打开。
在交互模式下,用户可以通过所创建的终端来输入命令,例如
root@af8bae53bdd3:/# pwd / root@af8bae53bdd3:/# ls bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
当利用 docker run 来创建容器时,Docker 在后台运行的标准操作包括:
检查本地是否存在指定的镜像,不存在就从公有仓库下载
利用镜像创建并启动一个容器
分配一个文件系统,并在只读的镜像层外面挂载一层可读写层
从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中去
从地址池配置一个 ip 地址给容器
执行用户指定的应用程序
执行完毕后容器被终止
启动已终止容器
可以利用 docker start 命令,直接将一个已经终止的容器启动运行。
容器的核心为所执行的应用程序,所需要的资源都是应用程序运行所必需的。除此之外,并没有其它的资源。可以在伪终端中利用 ps 或 top 来查看进程信息。
root@ba267838cc1b:/# ps PID TTY TIME CMD 1 ? 00:00:00 bash 11 ? 00:00:00 ps
可见,容器中仅运行了指定的 bash 应用。这种特点使得 Docker 对资源的利用率极高,是货真价实的轻量级虚拟化。
更多的时候,需要让 Docker 容器在后台以守护态(Daemonized)形式运行。此时,可以通过添加 -d 参数来实现。
例如下面的命令会在后台运行容器。
$ sudo docker run -d ubuntu:14.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"
1e5535038e285177d5214659a068137486f96ee5c2e85a4ac52dc83f2ebe4147
容器启动后会返回一个唯一的 id,也可以通过 docker ps 命令来查看容器信息。
$ sudo docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 1e5535038e28 ubuntu:14.04 /bin/sh -c 'while tr 2 minutes ago Up 1 minute insane_babbage
要获取容器的输出信息,可以通过 docker logs 命令。
$ sudo docker logs insane_babbage hello world hello world hello world
可以使用 docker stop 来终止一个运行中的容器。
此外,当Docker容器中指定的应用终结时,容器也自动终止。 例如对于上一章节中只启动了一个终端的容器,用户通过 exit 命令或 Ctrl+d 来退出终端时,所创建的容器立刻终止。
终止状态的容器可以用 docker ps -a 命令看到。例如
sudo docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES ba267838cc1b ubuntu:14.04 "/bin/bash" 30 minutes ago Exited (0) About a minute ago trusting_newton 98e5efa7d997 training/webapp:latest "python app.py" About an hour ago Exited (0) 34 minutes ago backstabbing_pike
处于终止状态的容器,可以通过 docker start 命令来重新启动。
此外,docker restart 命令会将一个运行态的容器终止,然后再重新启动它。
`提示:容器是一个特殊进程