《第一本Docker书》 读书笔记

在我看来,春天里一棵小草生长,它没有什么目的。风起时一匹公马发情,它也没有什么目的。草长马发情,绝非表演给什么人看的,这就是存在本身。我要抱着草长马发情的伟大真诚去做一切事,而不是在人前差羞答答的表演。在我看来,人都是为了要表演,失去了自己的存在。——王小波《三十而立》

写在前面


  • 一个之前的项目要去Oracle用teleDB上云。所以需要改好了sql,重新搞一下。拉一个Docker镜像部署一下。
  • dokcer没有系统的学习,之前都是应急,同时比较容易上手,所以都是简单学习。
  • 博文主要是《第一本Docker书》的一些读书笔记,基本是书里的东西,部分没有摘的内容,小伙伴可以移步:Docker、Podman 容器“扫盲“ 学习笔记

在我看来,春天里一棵小草生长,它没有什么目的。风起时一匹公马发情,它也没有什么目的。草长马发情,绝非表演给什么人看的,这就是存在本身。我要抱着草长马发情的伟大真诚去做一切事,而不是在人前差羞答答的表演。在我看来,人都是为了要表演,失去了自己的存在。——王小波《三十而立》

第1章简介

容器管理程序虚拟化(hypervisorvirtualization, HV)的不同.

  • 管理程序虚拟化即常说的虚拟机:通过中间层一台或多台独立的机器虚拟运行于物理硬件之上.
  • 容器则是直接运行在操作系统内核之上用户空间。因此,容器虚拟化也被称为“操作系统级虚拟化”,容器技术可以让多个独立的用户空间运行在同一台宿主机上。由于“客居”于操作系统,容器只能运行与底层宿主机相同或相似的操作系统。这看起来并不是非常灵活。例如,可以在Ubuntu服务器中运行RedHat Enterprise Linux,但却无法在Ubuntu服务器上运行Microsoft Windows.

相对于彻底隔离的管理程序虚拟化,容器被认为是不安全的。而反对这一观点的人则认为,由于虚拟机所虚拟的是一个完整的操作系统,这无疑增大了攻击范围,而且还要考虑管理程序层潜在的暴露风险。

尽管有诸多局限性,容器还是被广泛部署于各种各样的应用场合。在超大规模的多租户服务部署、轻量级沙盒以及对安全要求不太高的隔离环境中,容器技术非常流行。

  • 最常见的一个例子就是“权限隔离监牢“ (chroot jail),它创建一个隔离的目录环境来运行进程。如果权限隔离监牢中正在运行的进程被入侵者攻破,入侵者便会发现自己“身陷图围",因为权限不足被困在容器创建的目录中,无法对宿主机进行进一步的破坏

最新的容器技术引入了OpenVZ, Solaris Zones以及Linux容器(如lxc)。使用这些新技术,容器不再仅仅是一个单纯的运行环境。在自己的权限范围内,容器更像是一个完整的宿主机。对Docker来说,它得益于现代Linux内核特性,如控件组(control group)、命名空间(namespace)技术,容器和宿主机之间的隔离更加彻底,容器有独立的网络和存储栈,还拥有自己的资源管理能力,使得同一台宿主机中的多个容器可以友好地共存。

1.1 Docker简介

Docker是一个能够把开发的应用程序自动部署容器开源引擎。由Docker公司. (www.docke.com,前dotCloud公司, Paas市场中的老牌提供商)的团队编写,基于Apache .2.0开源授权协议发行。

1.1.1 提供一个简单、轻量的建模方式

Docker依赖于“写时复制" (copy-on-write)模型,Docker容器拥有很高的性能,同时同一台宿主机中也可以运行更多的容器,使用户可以尽可能充分地利用系统资源。

1.1.2 职责的逻辑分离

使用Docker,开发人员只需要关心容器运行的应用程序,而运维人员只需心如何管理容器。Docker 设计的目的 ,就是要 加强开发人员写代码的开发环境与应用程序要部署的生产环境的一致性,从而降低那种“开发时一切都正常,肯定是运维的问题”的风险。

1.1.3 快速、高效的开发生命周期

Docker的目标之一就是开发、到署、测试到部、上线运行的周期,让你的应用程序具备可移植性,易于构建,并易于协作

1.1.4 鼓励使用面向服务的架构

  • Docker还鼓励面向服务的架构和微服务架构。.
  • Docker推荐单个容器只运行一个应程序或进程,这样就形成了一个分布式的应用程序模型,在这种模型下,应用程序或服务都可以表示为一系列内部互联的容器,从而使分布式部署应用程序,扩展或调试应用程序都变得-非常简单,同时也提高了程序的内省性

1.2 Docker组件

Docker客户端和服务器,也成为Docker引擎;、Docker镜像;、Registry;、Docker容器。

1.2.1 Docker客户端和服务器

  • Docker是一个客户端/服务器(C/S)架构的程序。
  • Docker客户端只需向Docker服务器”或守护进程发出请求,服务器或守护进程将完成所有工作并返回结果。
  • Docker守护进程有时也称为Docker引擎。
  • Docker提供了一个命令行工具docker以及一整套RESTful API来与守护进程交互

1.2.2 Docker镜像

镜像是构建Docker世界的基石。**用户基于镜像来运行自己的容器**。
镜像也是Docker生命周期的 **“构建”部分**。镜像是基于联合(Union)文件系统一种层式的结构,)由一系列指令一步一步构建出来。例如:+ 添加一个文件;

  • 执行一个命令:
  • 打开一个端口。
    也可以把镜像当作 **容器的“源代码”**。镜像体积很小,非常“便携”,易于分享、存储和

1.2.3 Registry

Docker用Registry来保存用户构建的镜像。Begisty共和私有两种。
Docker公司运营的公共Registry 叫作Docker Hub。

1.2.4 容器

Docker可以帮用户构建和部署容器,用户只需要把自己的应用程序或服务打包放进容器即可。

容器是基于镜像启动起来的,容器中可以运一个进程。我们可以认为,镜像是Docker生命周期中的构建或打包阶段,而 **容器则是启动或执行阶段**。总结起来, Docker容器就是:

  • 一个镜像格式;·
  • 一系列标准的操作;
  • 一个执行环境。

==Dokeer借鉴了 **标准集装箱**的概念。标准集装箱将货物运往世界各地, Docker将这个模型运用到自己的设计哲学中,唯一不同的是:集装箱运输货物,而Docker运输软件。每个容器都包含一个软件镜像,也就是容器的“货物”,而且与真正的货物一样,容器里的软件镜像可以进行一些操作,,镜像可以被创建启动、关闭==

像标准集装箱一样, Docker容器方便替换,可以叠加,易于分发,并且尽量通用。使用Docker,可以快速构建一个应用程序服务器、一个消息总线、一套实用工具、持续集成( continuous integration, CI)测试环境或者任意一种应用程序、服务或工其。以在本地构建一个完整的测试环境,也可以为生产或开发快速复制一套复杂的应用程序可以说。

1.3 能用Docker做什么

  • 加速本地开发和构建流程,使其更加高效、更加轻量化。本地开发人员可以构建、运行并分享Docker容器。容器可以在开发环境中构建,然后轻松地提交到测试环境中,并最终进入生产环境,
  • 能够让独立服务或应用程序在不同的环境中,得到相同的运行结果。这一点在面向服务的架构和重度依赖微型服务的部署中尤其实用。
  • Docker创建隔离的环境来进行测试。例如,用Jenkins CI这样的持续集成工具启动一个用于测试的容器。
  • Docker可以让开发者先在本机上构建一个复杂的程序或架构来进行测试,而不是一开始就在生产环境部署、测试。
  • 构建一个多用户的平台即服务(Paas)基础设施。
  • 为开发、测试提供一个轻量级的独立沙盒环境,或者将独立的沙盒环境用于技术教学,如Unix shell的使用、编程语言教学。
  • 提供软件即服务(Saas)应用程序。
  • 高性能、超大规模的宿主机部署。

1.4 Docker与配置管理

Docker一个显著的特点就是,对不同的宿主机、应用程序和服务,可能会表现出不同的特性与架构(或者确切地说, Docker本就是被设计成这样的):

Docker可以是短生命周期的,但也可以用于恒定的环境,可以用一次即销毁,也可以提供持久的服务。这些行为并不会给Docker增加复杂性,也不会和配置管理工具的需求产生重合。基于这些行为,我们基本不需要担心管理状态的挂状态复杂性,因为容器的生命周期往往比较短,而且重建容器状态的代价通常也比传统的状态修复要低。

1.5 Docker的技术组件

Docker可以运行于在何安装了Linux内核的x64主机上。推荐的内核版是3.8或者更高。Docker的开销比,可以用于服务器、台式机或笔记本。包括以下;

  • 一个原生的Linux容器格式, Docker中称为1ibcontainer.
  • Linxu内核的命名空间(namespace) “,用于隔离文件系统、进程和网络。
  • 文件系统隔离:每个容器都有自己的root文件系统。
  • 进程隔离:每个容器都运行在自己的进程环境中。
  • 网络隔离:容器间的虚拟网络接口和IP地址都是分开的。
  • 资源隔离和分组:使用cgroups” (即control group, Linux的内核特性之一)将CPU和内存之类的资源独立分配给每个Docker容器。
  • 写时复制:文件系统都是通过写时复制创建的,这就意味着文件系统是分层的、快速的,而且占用的磁盘空间更小。
  • 日志:容器产生的STDOUT, STDERR和STDIN这些io流都会被收集并记入日志,用来进行日志分析和故障排错。交
  • 交互式shell:用户可以创建一个伪tty终端,将其连接到STDIN,为容器提供一个交互式的shell.

1.7 Docker资源

Docker官方主页(http://www.docker.com/)
Docker Hub (http:/hub.docker.com)
Docker官方博客(http://blog.docker.com/)
Docker官方文档(http://docs.docker.com/)。
Docker快速入门指南(http://www.docker.com/tryit/)
Docker的GitHub源代码(https://github.com/docker/docker)"
Docker Forge (https://github.com/dockerforge):

收集了各种Docker工具、组件和服务。
Docker邮件列表(https://groups.google.com/forum/#!forum/docker-user)
Docker的IRC频道(irc.freenode.net)
Docker的Twitter主页(http://twitter.com/docker)
Docker的StackOverflow问答主页(http://stackoverflow.com/search?q-docker).
Docker官网(http://www.docker.com/)

第2章安装Docker

Docker用户界面

Shipyard: Shipyard提供了通过管理界面来管理各种Docker资源(包括容器、镜像、宿主机等)的功能。Shipyard是开源的,源代码可以在https://github.com/ehazlett/shipyard获得pockerUl:
DpckerUI是一个可以与Docker Remote API交互的Web界面。DockerUI是基于AngulaJS框架,采用JavaScript编写的。
Kitematic: Kitematic是一个OS X和Windows下的GUI界面工具,用于帮助我们在本地运行Docker以及与Docker Hub进行交互。它是由Docker公司免费发布的产品,也被包含在Docker Toolbox之中。

第3章Docker入门

3.1确保Docker已经就绪

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[root@liruilong ~]# docker info
Containers: 4
Running: 2
Paused: 0
Stopped: 2
Images: 3
Server Version: 1.13.1
Storage Driver: overlay2
Backing Filesystem: extfs
Supports d_type: true
Native Overlay Diff: true
Logging Driver: journald
Cgroup Driver: systemd
Plugins:
Volume: local
Network: bridge host macvlan null overlay
Swarm: inactive
Runtimes: docker-runc runc
Default Runtime: docker-runc
Init Binary: /usr/libexec/docker/docker-init-current
containerd version: (expected: aa8187dbd3b7ad67d8e5e3a15115d3eef43a7ed1)

3.2运行我们的第一个容器

1
2
3
4
5
6
7
8
9
10
11
12
[root@liruilong ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
docker.io/rabbitmq management 485c275e2364 5 weeks ago 252 MB
docker.io/nginx latest 35c43ace9216 5 months ago 133 MB
docker.io/mamohr/centos-java latest e041132b8b32 3 years ago 577 MB
[root@liruilong ~]# docker run -i -t e041132b8b32 /bin/bash
[root@899c72cacb59 /]# ls
anaconda-post.log bin dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
[root@899c72cacb59 /]# uname
Linux
[root@899c72cacb59 /]#

  • -i标志保证容器中STDIN是开启的,尽管我们并没有附着到容器中。持久的标准输入是交互式shell的半边天。
  • -t标志则是另外,它docker为要创建的容器分配·一个伪tty终端。这样,新创建的容器才能提供一个交互式shell.若要在命令行下创建一个,我们能与之进行交互的容器,而不是一个运行后台服务的容器,则这两个参数已经是最基本的参数了。

Docker会检查本地是否是存在镜像,如果本地还没有该镜像的话,那么Docker就会连接官方维护的DotrHub Rggistry,查看Docker Hub中是否有该镜像。Docker一旦找到该镜像,就下载镜像并将共保存到本地宿主机中。

Docker在文件系统内部用这个镜像创全新容器。该容器拥有自己的网络IP地址/以及一个用来和宿主机进行通信 桥接网络接口。最后,我们告诉Docker在新容器中要运行什么命令,在本例中我们在容器行/bin/bash命令启动了一个Bash shell.

3.3使用第一个容器

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
[root@liruilong ~]# docker run -i -t e041132b8b32 /bin/bash
[root@e98c71f36f2d /]# hostname
e98c71f36f2d
[root@e98c71f36f2d /]# cat /etc/hosts
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.18.0.5 e98c71f36f2d
[root@e98c71f36f2d /]# yum provides ifconfig
Loaded plugins: fastestmirror, ovl
base | 3.6 kB 00:00:00
extras | 2.9 kB 00:00:00
updates | 2.9 kB 00:00:00
。。。。。。
net-tools-2.0-0.25.20131004git.el7.x86_64 : Basic networking tools
Repo : base
Matched from:
Filename : /sbin/ifconfig

[root@e98c71f36f2d /]# yum -y install net-tools-2.0-0.25.20131004git.el7.x86_64
Loaded plugins: fastestmirror, ovl
Loading mirror speeds from cached hostfile
。。。。
[root@e98c71f36f2d /]# ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.18.0.5 netmask 255.255.0.0 broadcast 0.0.0.0
inet6 fe80::42:acff:fe12:5 prefixlen 64 scopeid 0x20<link>
ether 02:42:ac:12:00:05 txqueuelen 0 (Ethernet)
RX packets 2797 bytes 30739967 (29.3 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 2625 bytes 176315 (172.1 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

[root@e98c71f36f2d /]# yum update && yum -y install vim

用户可以继续在容器中做任何自己想做的事情。当所有工作都结束时,输入exit,容器现在以经停止运行了!

在指定的/bin/bash命令处于运行状态的时候,我们的容器也才会地处于运行状态。一旦退出容器, /bin/bash命令也就结束了,这时容器随之停止了运行。但容器仍然是存在的,可以docker ps-a命查看当前系统中容器的列表,

有3种方式可以唯一指代容器:

  • 短DUD(如f7cbdac22a02)、
  • 长UUID(7cbdac02er3c9438c729345e54db9d20cfa2ac1fc3494b6eb60872e74778)
  • 名称(如gray-cat)。

3.4容器命名

1
$ sudo docker --name boy_the_container -i-t ubuntu /bin/bash

定器的命名必须是唯一的。如果试图创建两个名称相同的容器》则命令将会失败。如果要使用的容器名称已经存在,可以先用dockerrmf删除已有的同名容器后,再来创建新的容器。

3.5重新启动已经停止的容器

1
sudo docker start bpb the container

也可以使用docker restart命令来重新启动一个容器。这时运行不带-a标志的docker ps命令,就应该看到我们的容器已经开始运行了。注意类似地, Docker也提供(docker create命令来创建一个容器,但是并不运行它。这让我们可以在自己的容器工作流中对共进行细杠度的控制。

3.6附着到容器上

1
sudo docker attach container

3.7创建守护式容器

1
sudo docker run --name daemon_daved -d ubuntu /bin/sh-c "while true;do echo tettewera;seelp 1; done"

/usr/sbin/init 启动容器之后可以使用systemctl方法 ,-privileged=true 获取宿主机root权限(特殊权限-) su命令和su -命令最大的本质区别就是:前者只是切换了root身份,但Shell环境仍然是普通用户的Shell;而后者连用户和Shell环境一起切换成root身份了。

3.8容器内部都在干些什

1
sudo docker logs -f daemon dave

在这里插入图片描述

  • 我们也可以跟踪容器日志的某 段,和之前类似,只需要在tail命加入-f—tail标志即可。例如,可以
  • docker logs --tail 10 daemon_dave 获取日志最后10行内容。另外,也可以
  • docker logs --tail 0 -f daemon_dave 命令来追踪某个容器的最新日志
  • 还可-t标志为每条日志项加上时间戳
1
sudo docker logs -ft daemon dave

在这里插入图片描述

3.9 Docker日志驱动

Docker 1.6开始,也可以控制Docker守护进程和容器的日志驱动,--1og-driver选项现。可以在启动Docker守护进程或执行docker run命令

有好几个选项,包括默认json-file, json-file也为我 前面看到的docker logs命令提供了基础。

其他的选项还syslog,该选项将禁用docker logs命令,形 将所有容器志输 都重定向Syslog.可以在启动docker守护进程时指定该选项。

1
sudo docker run --name daemon_daved --log-driver="syslog" -d ubuntu /bin/sh -c "while true; do echo hello word; sleep 1; done"

取还有一个可用的选项是none,这个选项将会禁用所有容器中的日志,导致docker logs命令也被禁用

3.10查看容器内的进程

查看容器运行的讲程,要做到这一使用docker top

1
2
3
4
5
6
7
8
9

┌──(liruilong㉿Liruilong)-[/mnt/e/docker/uag/uag_jdk]
└─$ sudo docker top daemon_daved
PID USER TIME COMMAND
25931 root 0:00 /bin/sh -c while true; do echo hello word; sleep 1; done
26291 root 0:00 sleep 1

┌──(liruilong㉿Liruilong)-[/mnt/e/docker/uag/uag_jdk]
└─$

3.11 Docker统计信息

1
sudo docker stats daemon_daved

3.12在容器内部运行进程

在Docker 1.3之后,也可以通过docker exec命令在容器内部额外启动新进程。

  • 在容器内运行的进程有两种:后台任务和交互式任务。
  • 后台任务在容器内且没有交互需求,而交互式在务则保持在前台行。
  • 对于需要在容器内 打开shell的在务,互式务很实用的。
    1
    2
    3
    4
    5
    6
    7
    8
    ┌──(liruilong㉿Liruilong)-[/mnt/e/docker/uag/uag_jdk]
    └─$ sudo docker exec -d daemon_daved touch /etc/new_config_file
    ┌──(liruilong㉿Liruilong)-[/mnt/e/docker/uag/uag_jdk]
    └─$ sudo docker exec -ti daemon_daved /bin/bash
    root@232b4ca6dd68:/# ls
    bin boot dev etc home lib lib32 lib64 libx32 media mnt opt proc root run sbin srv sys tmp usr var
    root@232b4ca6dd68:/#

  • 这里的-t-i标志为我们执行的进程创TTY并捕护STDIN,接着我们指定了要在内部执行这个命令的容器的名字以及要执命。在上面的例子中,这条命令会在daemon-dave容器内创建一个新的bash会话,有了这个会话,我们就可以在该容器中运行其他命令了。

Docker 1.7开始,可以对docker exec启动的进程使用-u标志为新启动的进程指定,一个用户属主。

docker exec命令是Docker 1.3引入的,早期版本并不支持该命令。对于早期Docker版本,请参考第6章中介绍的nsenter命令。

3.13停止守护式容器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
┌──(liruilong㉿Liruilong)-[/mnt/e/docker/uag/uag_jdk]
└─$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
232b4ca6dd68 ubuntu "/bin/sh -c 'while t…" 17 minutes ago Up 17 minutes daemon_daved

┌──(liruilong㉿Liruilong)-[/mnt/e/docker/uag/uag_jdk]
└─$ sudo docker stop 232b4ca6dd68
232b4ca6dd68

┌──(liruilong㉿Liruilong)-[/mnt/e/docker/uag/uag_jdk]
└─$
└─$ docker ps -n 2
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
232b4ca6dd68 ubuntu "/bin/sh -c 'while t…" 18 minutes ago Exited (137) 42 seconds ago daemon_daved
166f786ffb04 9dbed5a04e9c "/bin/sh -c 'yum upd…" 5 hours ago Exited (127) 5 hours ago elegant_roentgen

3.14自动重启容器

1
2
3
4
5
6
┌──(liruilong㉿Liruilong)-[/mnt/e/docker/uag/uag_jdk]
└─$ sudo docker run --restart=always --name daemon_daved -d ubuntu /bin/sh -c "while true; do echo hello word; sleep 1; done"
c030ed0db34903039a104816094887d884bcefff767c2ac056bd8a64d294dce9

┌──(liruilong㉿Liruilong)-[/mnt/e/docker/uag/uag_jdk]
└─$

--restart标志被设置为always,无论容器退出代碼是什麽,Docker都会自动重启该容器,
还可以将这个标志为on-failure,这样,只有当容器的退出代码为非0值的时候)才会自动重启。
on-failure 还一个可选的重启次数.--restart=on-failure:4

3.15深入容器

通过docker ps命令获取容器的信息,还可以使用docker inspect来获得更多的容器信息,也可用-f或者--format标志来定查看结果

1
2
3
4
5
6
7
8
9
10
┌──(liruilong㉿Liruilong)-[/mnt/e/docker/uag/uag_jdk]
└─$ sudo docker inspect --format='{{ .State.Running }}' c030ed0db349
true

┌──(liruilong㉿Liruilong)-[/mnt/e/docker/uag/uag_jdk]
└─$ sudo docker inspect --format='{{ .NetworkSettings.IPAddress }}' c030ed0db349
172.17.0.2

┌──(liruilong㉿Liruilong)-[/mnt/e/docker/uag/uag_jdk]
└─$

也可以同时指定多个容器,并显示每个容器的输出结果,

3.16删除容器

如果容器已经不再使用,可以docker rm命令删除它们,
Docker 1.6.2开始,可以通过给docker rm命全传 -f标志来删除运行中的Docker

1
sudo docker rm `sudo docker ps -a -q`

-a标志代表列出所有容器,而-q标,志则表示只需要返回容器的ID而不会返回容器的其他信息。这样我们就得到了容器ID的列表,并传给了docker rm命令,从而达到删除所有容器的目的。

第4章 使用Docker镜像和仓库

4.1什么是Docker镜像

Docker镜像是由文件系统叠加而成,底端是一个引导文件系统 bootfsDocker用户几乎永远不会和引导文件交互。实际上,当一个容器启动.后,它将会被移到内存中,而引导文件系统则会被卸载(unmount),以留出更多的内存供initrd磁盘镜像使用。

Docker看起来还很像一个典型的Linux虚拟化栈。实际, Docker镜像的第二层是root文件系统rootfs, 位于引导文件系统之上。

rootfs可以或多种操作系如Debian或者ubuntu文件系统)。在传统的Linux引导过程中, root文件系统会最先以只读的方式加载,当引导结束并完成了完整性检查之后,它才会被切换为读写模式是在Docker里, root文件系统永远只能是只读状态,并且Docker利用联合加载(union mount)技术又会在root文件系统层上加载更多的只读文件系统

联合加载是指同时加载多个文件系统,但是在外面看起术只能看到只有一个文件系统。联合加载会将各层文件系统叠加到一起。

Docker将这样的文件系统称为镜像。一个镜像可以放到另一个镜像的顶部。位于下面的镜像称为父镜像(parent image),可以依次类推,直到镜像栈的最底部,最底部的镜像称为基础镜像(base image),最后,当从一个镜像启动容器时, Docker会在该镜像的最顶层加载一个读写文件系统。我们想在Docker中运行的程序就是在这个读写层中执行的。

在这里插入图片描述

Docker第一次启动一个容器时,初始的读写层的。当文件系统发生变化时,这些变化都会应用到这一层上。比如,如果想修改一个文件

  • 这个文件首先会从该读写层下面的只读层复制到该读写层。该文件的只读版本依然存在,但是已经被读写层中的该文件副本所隐藏。通常这种机制被称为写时复制(copy on write),这也是使Docker如此强大的技术之一。
  • 每个只读镜像层都是只读的,并且以后永远不会变化。当创建一个新容器时, Docker会构建出一个镜像栈,并在最顶端添加一个读写层。这个读写层再加上其下面的镜像层以及一些配置数据,就构成了一个容器。

4.2列出镜像

本地镜像都保存在Docker宿主机的/var/lib/docker目录下。每个镜像都保存在Docker所采用的存储驱动目录下面,如aufs或者devicemapper。也可以在/var/lib/docker/containers目录下面看到所有的容器。

1
docker images

4.3拉取镜像

1
docker pull image_name

4.4查找镜像

1
docker search image_name

4.5构建镜像

使用docker commit命令。使用docker build命令和Dockerfile文件。

1
docker commit 容器ID 镜像名

-旦有了Dockerfile,我们就可以使用docker build命令基于该Dockerfile中的指令构建一个新的镜像。

1
2
3
4
5
6
7
8
FROM ubuntu:18.04
LABEL maintainer="james@example.com"
RUN apt-get update
RUN apt-get install -y nginx
RUN echo 'Hi, I am in your container' \
>/var/www/html/index.html
EXPOSE 80

4.5.1 创建Docker Hub账号

4.5.2 用Docker的commit命令创建镜像

这个是基于已有的镜像的基础上新做一个。

4.5.3 用Dockerfile构建镜像

每条指令都会创建一个新的镜像层并对镜像进行提交Docker大体上按照如下流程执行Dockerfile中的指令。

  • Docker从基础镜像运行一个容器。
  • 执行一条指令,对容器做出修改。
  • 执行类似docker commit的操作,提交一个新的镜像层。
  • Docker再基于刚提交的镜像运行一个新容器。
  • 执行Dockerfile中的下一条指令,直到所有指令都执行完毕。

RUN指令会在shell里使用命令包装器/bin/sh -c来执行。如果是在一个不支持shell的平台上运行或者不希望在shell中运行(比如避免shell字符串篡改),也可以使用exec格式的RUN指令

1
RUN [ "apt-get", " install", "-y", "nginx"]

从Git仓库构建Docker镜像

1
$ sudo docker build-t="jamtur01/static web:v1" \ git@github.com: jamtur01/docker-static web

4.5.6 Dockerfile和构建缓存

由于每一步的构建过程都会将结果提交为镜像,所以Docker的构建镜像过程就显得非常聪明。它会将之前的镜像层看作缓存。

有些时候需要确保构建过程不会使用缓存。要想略过缓存功能,可以使用docker build的--no-cache标志

4.5.7 基于构建缓存的Dockerfile模板

构建缓存带来的一个好处就是,我们可以实现简单的Dockerfile模板(比如在Dockerfile文件顶部增加包仓库或者更新包,从而尽可能确保缓存命中),我一般都会在自己的Dockerfile文件顶部使用相同的指令集模板,比如对Ubuntu.使用模版。

1
2
3
4
FROM ubuntu:18.04
LABEL maintainer="james@example.com"
ENV REFRESHED_AT 2016-06-01
RUN apt-get -qq update

分析一下这个新的Dockerfile,首先,我通过FROM指令为新镜像设置了一个基础镜像ubuntu:14.04。接着,我又使用MAINTAINER指令添加了自己的详细联系信息。之后我又使用了一条新出现的指令ENV来在镜像中设置环境变量。在这个例子里,我通过ENV指令来设置了一个名为REFRESHED_AT的环境变量,这个环境变量用来表明该镜像模板最后的更新时间。最后,我使用了RUN指令来运行apt-get -qq update命令。该指令运行时将会刷新APT包的缓存,用来确保我们能将要安装的每个软件包都更新到最新版本。有了这个模板,如果想刷新一个构建,只需修改ENV指令中的日期。这使Docker在命中ENV指令时开始重置这个缓存,并运行后续指令而无须依赖该缓存

4.5.8查看新镜像

1
docker images 镜像名字

4.5.9从新镜像启动容器

1
docker run -it -d --name 容器名  镜像名 启动命令

4.5.10 Dockerfile指令

1.CMD

CMD指令用于指定一个容器启动时要运行的命令。这有点儿类似于RUN指令,**只是RUN指令是指定镜像被构建时要运行的命令,而CMD是指定容器被启动时要运行的命令**。这和使用docker run命令启动容器时指定要运行的命令非常类似

1
sudo docker run -it 镜像名  启动命令   // 等价与 CMD["启动命令"]

CMD["/bin/bash" , "-l"] 将 -l 作为参数传递进去,CMD 要运行的命令存放在一个数组结构中,告诉Docker 按照指定的原样来运行命令。

也可以直接使用命令,Dockerfile会在指定的命令前加上bin/sh -c(让 bash 将一个字串作为完整的命令来执行)

使用docker run命令可以覆盖CMD指令。如果我们在Dockerfile里指定了CMD指令,而同时在docker run命令行中也指定了要运行的命令,命令行中指定的命令会覆盖Dockerfile中的CMD指令。

Dockerfile中只能指定一条CMD指令。如果指定了多条CMD指令,也只有最后一·条CMD指令会被使用。如果想在启动容器时运行多个进程或者多条命令,可以考虑使用.类似Supervisor这样的服务管理工具。

2. ENTRYPOINT

ENTRYPOINT指令与CMD指令非常类似,也很容易和CMD指令弄混。这两个指令到底有什么区别呢?为什么要同时保留这两条指令?

**ENTRYPOINT指令提供的命令则不容易在启动容器时被覆盖。实际上, docker run命令行中指定的任何参数都会被当做参数再次传递给ENTRYPOINT指令中指定的命令**。

1
2
ENTRYPOINT [ "/usr/sbin/nginx" ]
ENTRYPOINT [ "/usr/sbin/nginx", "-g" , "daemon off;" ] # 通过-g参数临时指定一些全局配置项

使用ENTYRPOINT

1
2
ENTRYPOINT [ "usr/sbin/nginx" ]
sudo docker run -it nginx -g "daemon off;"

同时使用ENTRYPOINT和CMD

1
2
3
ENTRYPOINT [ "/usr/sbin/nginx" ]
CMD [ "-h" ]
# /usr/sbin/nginx -h 显示帮助信息

如果确实需要,用户也可以在运行时通过docker run的--entrypoint标志覆盖ENTRYPOINT指令。

3. WORKDIR

WORKDIR指令用来在从镜像创建一个新容器时,在容器内部设置一个工作目录,ENTRYPOINT和/或CMD指定的程序会在这个目录下执行。我们可以使用该指令为Dockerfile中后续的一系列指令设置工作目录,也可以为最终的容器设置工作目录

1
2
3
4
WORKDIR /opt/webapp/db
RUN bundle install
WORKDIR /opt/webapp
ENRTYPOINT [ "reckup" ]

这里,我们将工作目录切换为/opt/webapp/db后运行了bundle install命令,之后又将工作目录设置为/opt/webapp,最后设置了ENTRYPOINT指令来启动rackup命令。

可以通过-w标志在运行时覆盖工作目录.

1
2
sudo docker run -it -w /var/log ubuntu pwd
/var/log
4. ENV

ENV指令用来在镜像构建过程中设置环境变量,这个新的环境变量可以在后续的任何RUN指令中使用,这就如同在命令前面指定了环境变量前级一样,

1
ENV RVM_PATH /home/rvm

可以在ENV指令中指定单个环境变量,或者,从Docker 1.4开始可以指定多個变量

1
ENV APP_ID=Demo JAR_FILE_NAME="Demo-2.0.0-SNAPSHOT.jar"

也可以使用docker run命令行的-e标志来传递环境变量。这些变量将只会在运行时有效.

1
docker run -it -e "WEB_PORT=8080" centos env
5. USER

USER指令用来指定该镜像会以什么样的用户去运行

1
USER nginx

基于该镜像启动的容器会以nginx用户的身份来运行。我们可以指定用户名或UID以及组或GID,甚至是两者的组合.

1
2
3
4
5
6
USER user
USER user:group
USER uid
USER uid:gid
USER user:gid
USER uid:group

如果不通过USER指令指定用户,默认用户为root

6. VOLUME

VOLUME指令用来向基于镜像创建的容器添加卷。一个卷是可以存在于一个或者多个容器内的特定的目录,这个目录可以绕过联合文件系统,并提供如下共享数据或者对数据进行持久化的功能。

  • 卷可以在容器间共享和重用
  • 一个容器可以不是必须和其他容器共享卷。
  • 对卷的修改是立时生效的
  • 对卷的修改不会对更新镜像产生影响
  • 卷会一直存在直到没有任何容器再使用它

**卷功能让我们可以将数据(如源代码)、数据库或者其他内容添加到镜像中而不是将这些内容提交到镜像中,并且允许我们在多个容器间共享这些内容。我们可以利用此功能来测·试容器和内部的应用程序代码,管理日志,或者处理容器内部的数据库**。

1
2
VOLUME [ "/opt/project" ]
VOLUME [ "/opt/project" , "/data" ]

docker cp是和VOLUME指令相关并且也是很实用的命令。该命令允许从容器复制文件"和复制文件到容器上。可以从Docker命令行文档(https://docs.docker.com/engine/reference/commandline/cp/)中获得更多信息。

1
docker cp 源地址:目标地址  # 可以容器到宿主机,也可以宿主机到容器
7. ADD

ADD指令用来将构建环境下的文件和目录复制到镜像中。比如,在安装一个应用程序时。ADD指令需要源文件位置和目的文件位置两个参数

1
ADD ./target/*.tar.gz /root/

在ADD文件时, Docker通过目的地址参数末尾的字符来判断文件源是目录还是文件。如果目的地址以/结尾,那么Docker就认为源位置指向的是目录。如果目的地址不是以/结尾,那么Docker就认为源位置指向的是文件。 文件源也可以使用URL的格式.

最后值得一提的是, ADD在处理本地归档文件(tar archive)时还有一些小魔法。如果将一个归档文件(合法的归档文件包括gzip, bzip2、 xz)指定为源文件, Docker会自动将归档文件解开(unpack)

  • 如果目的位置不存在的话, Docker将会为我们创建这个全路径,包括路径中的任何目录。新创建的文件和目录的模式为0755,并且UID和GID都是0.

  • ADD指令会使得构建缓存变得无效,。如果通过ADD指令向镜像添加’个文件或者目录,那么这将使Dockerfile中的后续指令都不能继续使用之前的构建缓存。

8. COPY

COPY指令非常类似于ADD,它们根本的不同是COPY只关心在构建上下文中复制本地文件,而不会去做文件提取(extraction)和解压(decompression)的工作。

1
COPY conf.d/ /etc/apache2/  # 把本地conf.d目录中的文件复制到/etc/apache2/目录

文件源路径必须是一个与当前构建环境相对的文件或者目录,本地文件都放到和Dockerfile同一个目录下。不能复制该目录之外的任何文件,因为构建环境将会上传到Docker守护进程,而复制是在Docker守护进程中进行的。任何位于构建环境之外的东西都是不可用的

**COPY指令的目的位置则必须是容器内部的一个绝对路径**。任何由该指令创建的文件或者目录的UID和GID都会设置为0.

如果源路径是一个目录,那么这个目录将整个被复制到容器中,包括文件系统元数据;如果源文件为任何类型的文件,则该文件会随同元数据一起被复制。在这个例子里,源路径以/结尾,所以Docker会认为它是目录,并将它复制到目的目录中。

如果目的位置不存在, Docker将会自动创建所有需要的目录结构,就像mkdir-p命令那样。

9. LABEL

LABEL指令是在Docker 1.6版本中引入的。

LABEL指令以label="value"的形式出现。可以在每一条指令中指定一个元数据,或者指定多个元数据,不同的元数据之间用空格分隔。

推荐将所有的元数据都放到一条LABEL指令中,以防止不同的元数据指令创建过多镜像层。可以通过docker inspect命令来查看Docker镜像中的标签信息

LABEL指令用于为Docker镜像添加元数据。元数据以键值对的形式展现。

1
2
3
4
5
6
7
8
9
10
┌──(liruilong㉿Liruilong)-[/mnt/e/docker/uag/uag_nginx]
└─$ cat Dockerfile
FROM nginx
LABEL maintainer="uag"
ENV REFRESHED_AT 2021-08-27


VOLUME /var/log/nginx/
EXPOSE 80

1
2
3
4
5
docker inspect nginx_log  // 
"Labels": {
"desktop.docker.io/wsl-distro": "kali-linux",
"maintainer": "uag"
},
10. STOPSIGNAL

**STOPSIGNAL指令用来设置停止容器时发送什么系统调用信号给容器**。这个信号必须·是内核系统调用表中合法的数,如9,或者SIGNAME格式中的信号名称,如SIGKILL注意STOPSIGNAL指令是在Docker 1.9版本中引入的。

11. ARG

**ARG指令用来定义可以在docker build命令运行时传递给构建运行时的变量,**我们只需要在构建时使用--build-arg标志即可。用户只能在构建时指定在Dockerfile文件中定义过的参数。

1
2
ARG build
ARG webapp_user=user
1
docker build --build-arg build=1234 -t jamtur01/webapp .

ARG指令是在Docker 1.9版本中引入的,Docker预定义了一组ARG变量,可以在构建时直接使用,而不必再到Dockerfile中自行定义。

1
2
3
4
5
6
7
8
HTTP_PROXY
http_proxy
HTTPS_PROXY
https_proxy
FTP_PROXY
ftp_proxy
NO_PROXY
no_proxy
12. ONBUIID

ONBUILD指令能为镜像添加触发器(trigger)。**当一个镜像被用做其他镜像的基础镜像时(比如用户的镜像需要从某未准备好的位置添加源代码,或者用户需要执行特定于构建镜像的环境的构建脚本),该镜像中的触发器将会被执行**。触发器会在构建过程中插入新指令,我们可以认为这些指令是紧跟在FROM之后指定的。触发器可以是任何构建指令,

1
2
ONBUILD ADD .  /app/src 
ONBUILD cd /app/src && make

上面的代码将会在创建的镜像中加入ONBUILD触发器, ONBUILD指令可以在镜像上行docker inspect命令来查看,+

4.6将镜像推送到DockerHub

在这里插入图片描述

嗯,需要注册一个Docker Hub账号,然后登录,需要镜像前面加 账户名/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
┌──(liruilong㉿Liruilong)-[/mnt/e/docker/uag/uag_nginx]
└─$ docker push liruilong/nginx_log
The push refers to repository [docker.io/liruilong/nginx_log]
An image does not exist locally with the tag: liruilong/nginx_log

┌──(liruilong㉿Liruilong)-[/mnt/e/docker/uag/uag_nginx]
└─$ docker tag 9c9af0362eb9 liruilong/nginx_log

┌──(liruilong㉿Liruilong)-[/mnt/e/docker/uag/uag_nginx]
└─$ docker push liruilong/nginx_log
The push refers to repository [docker.io/liruilong/nginx_log]
fb04ab8effa8: Pushed
8f736d52032f: Pushed
009f1d338b57: Pushed
678bbd796838: Pushed
d1279c519351: Pushed
f68ef921efae: Pushed
latest: digest: sha256:2af7e8aeab84e8a816caf6b0342e1a45f95c7089ff52578040ea3a4c28a943c7 size: 1570

┌──(liruilong㉿Liruilong)-[/mnt/e/docker/uag/uag_nginx]
└─$ docker push liruilong/nginx_log:tagname # 拉去镜像

在这里插入图片描述

4.7删除镜像

1
2
3
docker rmi 镜像名
# 删除所有镜像
docker rmi `docker images -a -q`

4.8运行自己的DockerRegistry

希望构建和存储包含不想被公开的信息或数据的镜像。这时候我们有以下两种选择。

  • 利用Docker Hub上的私有仓库。
  • 在防火墙后面运行你自己的Registry。

4.8.1从容器运行Registry

Docker 1.3.1开始,需要在启动Docker守护进程的命令中添加--insecurereqistry localhost: 5000标志,并重启守护进程,才能使用本地Registry。

1
docker run -p 5000:5000 registry:2

4.8.2 测试新Registry

1
2
docker images 
docker tag 镜像ID 标签:镜像名
1
2
3
4
5
6
7
┌──(liruilong㉿Liruilong)-[/mnt/e/docker/uag/uag_nginx]
└─$ docker images registry
REPOSITORY TAG IMAGE ID CREATED SIZE
registry 2 1fd8e1b0bb7e 4 months ago 26.2MB

┌──(liruilong㉿Liruilong)-[/mnt/e/docker/uag/uag_nginx]
└─$ docker tag 1fd8e1b0bb7e docker.example.com:5000/liruilong/static_web

4.9其他可选Registry服务

也有很多其他公司和服务提供定制的Docker Registry服务。

Quay

Quay”服务提供了私有的Registry托管服务,允许用户上传公共的或者私有的容器。

目前它提供了免费的无限制的公共仓库托管服务,如果想托管私有仓库,它还提供了一系列的可伸缩计划。Quay最近被CoreOs收购了,并会被整合到他们的产品中去。

第5章在测试中使用Docker

  • 使用Docker测试一个静态网站。
  • 使用Docker创建并测试一个Web应用。
  • 将Docker用于持续集成。

5.1使用Docker测试静态网站(Nginx docker 化)

使用Nginx Web服务器安装到容器来架构一个简单的网站开始。这个网站暂且命名为Sample.

5.1.1 Sample网站的初始Dockerfile

获取Nginx配置文件

global.conf

1
2
3
4
5
6
7
8
9
10
11
server {
listen 0.0.0.0:80;
server_name _;

root /var/www/html/website;
index index.html index.htm;

access_log /var/log/nginx/default_access.log;
error_log /var/log/nginx/default_error.log;
}

nginx.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
user www-data;
worker_processes 4;
pid /run/nginx.pid;
daemon off;

events { }

http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
gzip on;
gzip_disable "msie6";
include /etc/nginx/conf.d/*.conf;
}

我们还需要将Nginx配置为非守护进程的模式,这样可以让Nginx在Docker容器里工作。

网站测试的基本Dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
FROM ubuntu:18.04
LABEL maintainer="james@example.com"
ENV REFRESHED_AT 2014-06-01

RUN apt-get -qq update && apt-get -qq install nginx

RUN mkdir -p /var/www/html/website
ADD nginx/global.conf /etc/nginx/conf.d/
ADD nginx/nginx.conf /etc/nginx/nginx.conf

EXPOSE 80

这个简单的Dockerfile内容包括以下几项。

  • 安装Nginx.·
  • 在容器中创建一个目录/var/www/html/website/
  • 将来自我们下载的本地文件的Nginx配置文件添加到镜像中。
  • 公开镜像的80端口。

这个Nginx配置文件是为了运行Sample网站而配置的。将文件nginx/global.conf用ADD指令复制到/etc/nginx/conf.d/目录中。

在nginx.conf这个配置文件里, daemon off;选项阻止Nginx进入后台,强制其在前台运行。这是因为要想保持Docker容器的活跃状态,需要其中运行的进程不能中断。默认情况下, Nginx"会以守护进程的方式启动,这会导致容器只是短暂运行,在守护进程被fork启动后,发起守护进程的原始进程就会退出,这时容器就停止运行了。

5.1.2构建Sample网站和Nginx镜像

1
2
3
4
5
6
7
8
9
10
11
12
┌──(liruilong㉿Liruilong)-[/mnt/e/docker/logstash/sample]
└─$ sudo docker build -t jamtur01/nginx .
Sending build context to Docker daemon 4.608kB
Step 1/8 : FROM ubuntu:18.04
---> 39a8cfeef173
Step 2/8 : LABEL maintainer="james@example.com"
---> Using cache
....
Removing intermediate container 15c4f81371e1
---> 1583c3126a40
Successfully built 1583c3126a40
Successfully tagged jamtur01/nginx:latest

在这里插入图片描述

5.1.3从Sample网站和Nainx镜像构建容器

1
2
3
4
5
┌──(liruilong㉿Liruilong)-[/mnt/e/docker/sample]
└─$ sudo docker run -d -p 81:80 --name website -v $PWD/website:/var/www/html/website jamtur01/nginx nginx
7bb1eaa70f46e565b940b83fd0129ad9d5b2c035278d7d9c2704187e08f4e43f

┌──(liruilong㉿Liruilong)-[/mnt/e/docker/sample]

可以看到,我们使用docker run命令从jamtur01/nginx镜像创建了一个名为website的容器。

  • -v这个选项允许我们将宿主机的目录作为卷,挂载到容器里,卷在Docker里非常重要,也很有用。**卷是在一个或者多个容器内被选定的目录,可以绕过分层的联合文件系统(Union FileSystem),为Docker提供持久数据或者共享数据**。这意味着对卷的修改会直接生效,并绕过镜像。当提交或者创建镜像时,卷不被包含在镜像里.

卷可以在容器间共享即便容器停止,卷里的内容依旧存在

当我们因为某些原因不想把应用或者代码构建到镜像中时,就体现出卷的价值了。例如:希望同时对代码做开发和测试;代码改动很频繁,不想在开发过程中重构镜像;希望在多个容器间共享代码

1
sudo docker run -d -p 81:80 --name website -v $PWD/website:/var/www/html/website:ro  jamtur01/nginx nginx

-v选项通过指定一个目录或者登上与容器上与该目录分离的本地宿主机来工作,这两个目录用:分隔。如果容器目录不存在,Docker会自动创建一个。也可以通过在目录后面加上rw或者ro来指定容器内目录的读写状态

5.1.4修改网站

这个很简单,不记录了

5.2使用Docker构建并测试Web应用程序

测试一个基于Sinatra的Web应用程序,而不是静态网站,然后我们将基于Docker来对这个应用进行测试。

Sinatra是一个基于Ruby的Web应用框架,它包含一个Web应用库,以及简单的领域专用语言(即DSL)来构建Web应用程序。与其他复杂的Web应用框架(如Ruby on Rails)不同, Sinatra并不遵循MVC模式,而关注于让开发者创建快速、简单的Web应用。

Sinatra非常适合用来创建一个小型的示例应用进行测试。在这个例子里,我们将创建一个应用程序,它接收输入的URL参数,并以JSON散列的结构输出到客户端。通过这个例子,我们也将展示一下如何将Docker容器链接起来。

5.2.1 构建Sinatra应用程序

我们先来创建一个sinatra目录,用来存放应用程序的代码,以及构建时我们所需的文件。

1
2
3
4
5
6
7
8
9
10
11
12
FROM ubuntu:14.04
LABEL maintainer="james@example.com"
ENV REFRESHED_AT 2014-06-01

RUN apt-get update && apt-get -y install ruby ruby-dev build-essential redis-tools
RUN gem install --no-rdoc --no-ri sinatra json redis

RUN mkdir -p /opt/webapp

EXPOSE 4567

CMD [ "/opt/webapp/bin/webapp" ]

在这里插入图片描述

5.2.2 创建Sinatra容器

下载web应用程序

1
2
3
4
5
┌──(liruilong㉿Liruilong)-[/mnt/e/docker/sinatra]
└─$ ls -l webapp
total 0
drwxrwxrwx 1 liruilong liruilong 4096 Aug 24 22:50 bin
drwxrwxrwx 1 liruilong liruilong 4096 Aug 24 22:50 lib

启动一个Sinatra容器

1
sudo docker run -d -p 4567 --name webapp -v $PWD/webapp:/opt/webapp jamtur01/sinatra

查看日志,进程:

1
2
3
4
docker logs webapp
docker top webapp
# 检查端口映射
docker port webapp 4567

嗯,这个不知道啥原因,这个镜像没有拉下来,所以这部分没实际的搞,但是应该很简单。

5.2.3 扩展Sinatra应用程序来使用Redis

5.2.4 将Sinatra应用程序连接到Redis容器

现在来更新Sinatra应用程序,让其连接到Redis并存储传入的参数。为此,需要能够与Redis服务器对话。要做到这一点,可以用以下几种方法。

  • Docker的内部网络。
  • Docker 1.9及之后的版本开始,可以使用Docker Networking以及docker network命令。
  • Docker链接。一个可以将具体容器链接到一起来进行通信的抽象层。

两种比较现实的连接Docker容器的方式是

  • Docker Networking:如果用户正在使用Docker1.9或者更新的版本,推荐使用Docker Networking
  • Docker链接(Dockerlink):如果使用的是Docker 1.9之前的版本,应该选择Docker链接

在Docker Networking和Docker链接之间也有一些区别:

  • Docker Networking可以将容器连接到不同宿主机上的容器。通过Docker Networking连接的容器可以在无需更新连接的情况下,对停止、启动或者重启容器。
  • 使用Docker链接,则可能需要更新一些配置,或者重启相应的容器来维护Docker容器之间的链接。
  • 使用Docker Networking,不必事先创建容器再去连接它。同样,也不必关心容器的运行顺序,读者可以在网络内部获得容器名解析和发现。

5.2.5 Docker内部连网

  • Docker自己的网络栈。到目前为止, 我们看到的Docker容器都是公开端口并绑定到本地网络接口的,这样可以把容器里的服务在本地Docker宿主机所在的外部网络上(比如,把容器里的80端口绑到本地宿主机的更高端口上)公开

  • 内部网络。在安装Docker时,会创建一个新的网络接口,名字是docker0,每个Docker容器都会在这个接口上分配一个IP地址。来看看目前Docker宿主机上这个网络接口的信息。

    1
    2
    3
    4
    5
    [root@liruilong ~]# ip a show  docker0
    3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:cc:79:42:bd brd ff:ff:ff:ff:ff:ff
    inet 172.18.0.1/16 scope global docker0
    valid_lft forever preferred_lft forever

    Docker每创建一个容器就会创建一组互联的网络接口。这组接口就像管道的两端(就是说,从一端发送的数据会在另一端接收到)。这组接口其中一端作为容器里的eth0接口,而另一端统一命名为类似vethec6a这种名字,作为宿主机的一个端口。**可以把veth* 接口认为是虚拟网线的一端。这个虚拟网线一端插在名为dockero的网桥上,另一端插到容器里。通过把每个veth接口绑定到docker0网桥, Docker创建了一个虚拟子网,这个子网由宿主机和所有的Docker容器共享。*

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    [root@liruilong ~]# docker run -ti centos /bin/bash
    Unable to find image 'centos:latest' locally
    Trying to pull repository docker.io/library/centos ...
    latest: Pulling from docker.io/library/centos
    7a0437f04f83: Pull complete
    Digest: sha256:5528e8b1b1719d34604c87e11dcd1c0a20bedf46e83b5632cdeac91b8c04efc1
    Status: Downloaded newer image for docker.io/centos:latest
    [root@badb10b3f287 /]# ip a show eth0
    77: eth0@if78: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:ac:12:00:05 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.18.0.5/16 scope global eth0
    valid_lft forever preferred_lft forever
    inet6 fe80::42:acff:fe12:5/64 scope link
    valid_lft forever preferred_lft forever
    [root@badb10b3f287 /]#

    可以看到, Docker给容器分配了IP地址172.18.0.5/16作为宿主虚拟接口的另一端。这样就能够让宿主网络和容器互相通信了。让我们从容器内跟踪对外通信的路由,看看是如何建立连接的.

1
2
3
4
[root@badb10b3f287 /]# yum update && yum install -y traceroute
[root@badb10b3f287 /]# traceroute www.baodu.com
traceroute to www.baodu.com (115.29.223.128), 30 hops max, 60 byte packets
1 _gateway (172.18.0.1) 0.034 ms 0.011 ms 0.009 ms

在这里插入图片描述

不过Docker网络还有另一个部分配置才能允许建立连接:防火墙规则和NAT配置。这些配置允许Docker在宿主网络和容器间路由。现在来查看一下宿主机上的IPTables NAT配置

因此,虽然第一眼看上去这是让容器互联的一个好方案,但可惜的是,这种方法有两个大问题:

  • 第一,要在应用程序里对Redis容器的IP地址做硬编码:
  • 第二,如果重启容器,Docker会改变容器的IP地址。

5.2.6 DockerNetworking

容器之间的连接用网络创建,这被称为Docker Networking,也是Docker 1.9发布版本中的一个新特性。

Docker Networking允许用户创建自己的网络,容器可以通过这个网上互相通信。实质上, Docker Networking以新的用户管理的网络补充了现有的docker0,更重要的是,现在容器可以跨越不同的宿主机来通信,并且网络配置可以更灵活地定制。DockerNetworking也和Docker Compose以及Swarm进行了集成.

创建 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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
docker network create NetWorkUag
docker network inspect NetWorkUag
[
{
"Name": "NetWorkUag",
"Id": "b328ad9d659e51cdd5b9b519909101b63d88f91a1a3ace3760810df5d14ef6a9",
"Created": "2021-08-31T08:03:13.944961308Z",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "172.20.0.0/16",
"Gateway": "172.20.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"6dfd37fd7083c5cc224565ffef6657b40cf563699dc328889a79f236208c014a": {
"Name": "uag_app_1",
"EndpointID": "d5a9f10ddcb10cd2002cc22606f34f9fe6c34e46b1f32a91489f506fc06cd6ad",
"MacAddress": "02:42:ac:14:00:02",
"IPv4Address": "172.20.0.2/16",
"IPv6Address": ""
},
"736bcc999a991dcc7d3113d82845b1e162bd4bd51ba25a0a88af30098a860dd5": {
"Name": "uag_app_2",
"EndpointID": "7c9eddf8f2926246e06431667acdb1012f3b7cb69cb5b248e0b079b7d8520fb8",
"MacAddress": "02:42:ac:14:00:03",
"IPv4Address": "172.20.0.3/16",
"IPv6Address": ""
},
"9b1bc7fe929bf2487a2a3560d6001960c5eccf7614626d7c7944372681c7e101": {
"Name": "uag_app_3",
"EndpointID": "0e387e8245ac216f330ba597ee6e38cda40c3eb1dd0da10c404cf2461a01fe86",
"MacAddress": "02:42:ac:14:00:04",
"IPv4Address": "172.20.0.4/16",
"IPv6Address": ""
},
"9d73f68b83dc731271668d63995ff6439be22b2c76c401a1b9f97d9fa1501613": {
"Name": "uag_nginx",
"EndpointID": "b036d6923cb8bb769fdc60fb9e2370832c9dd26daec9e2d26d957affee7a17f6",
"MacAddress": "02:42:ac:14:00:05",
"IPv4Address": "172.20.0.5/16",
"IPv6Address": ""
}
},
"Options": {},
"Labels": {}
}
]

除了运行于单个主机之上的桥接网络,我们也可以创建一个overlay网络, overlay"网络允许我们跨多台宿主机进行通信。

Docker在默认情况下,分别会建立一个bridge、一个host和一个none的网络:

网络模式简介

Host 容器将不会虚拟出自己的网卡,配置自己的IP等,而是使用宿主机的IP和端口.
Bridge 此模式会为每一个容器分配、设置IP等,并将容器连接到一个docker0虚拟网桥,通过docker0网桥以及iptables nat表配置与宿主机通信
None 该模式关闭了容器的网络功能
Container 创建的容器不会创建自己的网卡,配置自己的IP,而是和一个指定的容器共享

Docker内置这三个网络,运行容器时,你可以使用该–network标志来指定容器应连接到哪些网络,该bridge网络代表docker0所有Docker安装中存在的网络(默认使用桥接),除非你使用该docker run --network=选项指定,否则Docker守护程序默认将容器连接到此网络

列出当前系统的所有网络

1
2
3
4
5
6
7
8
9
10
11
12
┌──(liruilong㉿Liruilong)-[/mnt/e/docker/uag/uag_nginx]
└─$ docker network ls
NETWORK ID NAME DRIVER SCOPE
b328ad9d659e NetWorkUag bridge local
05db24cdbfe4 bridge bridge local
017b16936585 express bridge local
0f48492ddb93 host host local
ac3009db29a8 none null local
75656b057386 uag_net bridge local

┌──(liruilong㉿Liruilong)-[/mnt/e/docker/uag/uag_nginx]
└─$

–net标志指定了新容器将会在哪个网络中运行。

1
docker run -it -d -p 8069:8080 --net NetWorkUag --name uag_app_3  417264b76b2b

network这里书里的Demo,镜像一直没搞好,所以自己搞了一个,感兴趣小伙伴可以移步:基于Docker部署 Tomcat集群、 Nginx负载均衡

将已有容器连接到Docker网络也可以将正在运行的容器通过docker network connect命令添加到已有的网络中。

1
docker network connect 网络名 容器名

断开

1
docker network disconnect 网络名 容器名

**·一个容器可以同时隶属于多个Dcoker网络,所以可以创建非常复杂的网络模型**。
2. 通过Docker 链接来连接容器
这块时间原因,只做简单整理:

1
docker run -p 4561 --name webapp --link redis:db -it 镜像名

--link标志创建了两个容器间的客户-服务链接。这个标志需要两个参数:一个是要链接的容器的名字,另一个是链接的别名。

这个例子中,我们创建了客户联系, webapp容器是客户, redis容器是“服务”,并且为这个服务增加了db作为别名。这个别名让我们可以一致地访问容器公开的信息,而无须关注底层容器的名字。

链接让服务容器有能力与客户容器通信,并且能分享一些连接细节,这些细节有助于在应用程序中配置并使用这个链接。连接也能得到一些安全上的好处。

启动Redis容器时,并没有使用-p标志公开Redis的端口。因为不需要这么做。通过把容器链接在一起,可以让客户容器直接访问任意服务容器的公开端口(即客户webapp容器可以连接到服务redis容器的6379端口).

只有使用--1ink标志链接到这个容器的容器才能连接到这个端口容器的端口不需要对本地宿主机公开,现在我们已经拥有一个非常安全的模型,通过这个安全模型,就可以限制容器化应用程序被攻击面,减少应用暴露的网络。

如果用户希望,出于安全原因(或者其他原因),可以强制Docker只允许有链接的容器之,间互相通信。为此,可以在启动Docker守护进程时加上--icc=false标志,关闭所有没有链接的容器间的通信。

查看容器是如何链接在一起的。 Docker在父容器里的以下两个地方写入了链接信息。

  • /etc/hosts文件中。
  • 包含连接信息的环境变量中。

容器的主机名也可以不是其ID的一部分。可以在执行docker run命令时使用-h或者--hostname标志来为容器设定主机名。

如果在运行容器时指定--add-host选项,也可以在/etc/hosts文件中添加相应的·记录。例如,我们可能想添加运行Docker的主机的主机名和IP地址到容器中,

1
docker run -it --add-host=docker:10.0.0.1 .....

5.2.7 使用容器连接来通信

那么如何使用这个连接呢?有以下两种方法可以让应用程序连接到Redis.

使用环境变量里的一些连接信息。
在这里插入图片描述
在这里插入图片描述
使用DNS和/etc/hosts信息。
在这里插入图片描述
也可以在docker run命令中加入--dns或者--dns-search标志来为某个容器单独配置DNS。你可以设置本地DNS解析的路径和搜索城。在https:/docs.docker.com/articlesnetworking/上可以找到更详细的配置信息。如果没有这两个标志, Docker会根据宿主机的信息来配置DNS解析。可以在/etc/resolv.conf文件中查看DNS解析的配置情况。

5.3 Docker用于持续集成

第6章使用Docker构建服务

6.1 构建第一个应用

使用Jekyll框架的自定义网站。我们会构建以下两个镜像。

  • 一个镜像安装了Jekyll及其他用于构建Jekyll网站的必要的软件包。
  • 一个镜像通过Apache来让Jekyll网站工作起来。

我们打算在启动容器时,通过创建一个新的Jekyll网站来实现自服务。工作流程如下。

  • 创建Jekyll基础镜像和Apache镜像(只需要构建一次)。
  • 从Jekyll镜像创建一个容器,这个容器存放通过卷挂载的网站源代码。
  • 从Apache镜像创建一个容器,这个容器利用包含编译后的网站的卷,并为其服务。
    在网站需要更新时,清理并重复上面的步骤。

可以把这个例子看作是创建一个多主机站点最简单的方法。实现很简单,本章后半部分会以这个例子为基础做更多扩展。

6.1.1 Jekyll基础镜像

编写jekyll对应的Dockerfile文件

1
2
3
4
5
6
7
8
9
10
11
12
13
FROM ubuntu:18.04
LABEL maintainer="james@example.com"
ENV REFRESHED_AT 2016-06-01

RUN apt-get -qq update
RUN apt-get -qq install ruby ruby-dev libffi-dev build-essential nodejs
RUN gem install --no-rdoc --no-ri jekyll -v 2.5.3

VOLUME /data
VOLUME /var/www/html
WORKDIR /data

ENTRYPOINT [ "jekyll", "build", "--destination=/var/www/html" ]
  • 镜像基于Ubuntu 14.04,并且安装了Ruby和用于支持Jekyll的包。然后我们使用VOLUME指令创建了以下两个卷。,/data/,用来存放网站的源代码。./var/www/htm1/,用来存放编译后的Jekyll网站码然后我们需要将工作目录设置到/data/,并通过ENTRYPOINT指令指定自动构建的命令,这个命令会将工作目录/data/中的所有的Jekyll网站代码构建到/var/www/html/目录中。

6.1.2 构建Jekyl基础镜像

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
PS E:\docker> bash
┏━(Message from Kali developers)

┃ This is a minimal installation of Kali Linux, you likely
┃ want to install supplementary tools. Learn how:
┃ ⇒ https://www.kali.org/docs/troubleshooting/common-minimum-setup/

┗━(Run: “touch ~/.hushlogin” to hide this message)
┌──(liruilong㉿Liruilong)-[/mnt/e/docker]
└─$ mkdir jekyll

┌──(liruilong㉿Liruilong)-[/mnt/e/docker]
└─$ cd jekyll/;vim Dockerfile
┌──(liruilong㉿Liruilong)-[/mnt/e/docker/jekyll]
└─$ sudo docker build -t jamtur01/jekyll .
[sudo] password for liruilong:
Sending build context to Docker daemon 2.048kB
Step 1/10 : FROM ubuntu:18.04
18.04: Pulling from library/ubuntu
feac53061382: Pull complete
Digest: sha256:7bd7a9ca99f868bf69c4b6212f64f2af8e243f97ba13abb3e641e03a7ceb59e8
Status: Downloaded newer image for ubuntu:18.04
---> 39a8cfeef173
Step 2/10 : LABEL maintainer="james@example.com"
---> Running in d11e8cdaf982
Removing intermediate container d11e8cdaf982
。。。。

6.1.3 Apache镜像

构建 apache Dockerfile文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
RUN apt-get -qq update
RUN apt-get -qq install apache2
VOLUME ["/var/www/html"]
WORKDIR /var/www/html

# 定义环境变量
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

# 运行命令
RUN mkdir -p $APACHE_RUN_DIP $APACHE_LOCK_DIR $APACHE_LOG_DIR
# 暴露端口
EXPOSE 80
# 执行脚本
ENTRYPOINT [ "/usr/sbin/apachectl" ]
# 脚本执行参数
CMD [ "-D", "FOREGROUND"]
  • 这个镜像也是基于Ubuntu 14.04的,并安装了Apache。然后我们使用VOLUME指令创建了一个卷,即/var/www/html/,用来存放编译后的Jekyll网站。
  • 将/var/www/html设为工作目录。然后我们使用ENV指令设置了一些必要的环境变量,创建了必要的目录,并且使用EXPOSE公开了80端口。最后指定了ENTRYPOINT和CMD指令组合来在容器启动时默认运行Apache

6.1.4 构建Jekyll Apache镜像

1
2
3
4
5
6
7
8
9
10
11
12
13
┌──(liruilong㉿Liruilong)-[/mnt/e/docker/apache]
└─$ sudo docker build -t jamtur01/apache .
Sending build context to Docker daemon 2.56kB
Step 1/16 : FROM ubuntu:18.04
---> 39a8cfeef173
Step 2/16 : LABEL maintainer="james@example.com"
---> Using cache
---> 4a85ddbb7c6b
Step 3/16 : RUN apt-get -qq update
---> Running in 7de58bfe1ad6
.....
Successfully built 2d17448b9728
Successfully tagged jamtur01/apache:latest

6.1.5 启动Jekyll网站

1
2
3
4
5
6
7
PS E:\docker\jekyll> git clone git@github.com:turnbullpress/james_blog.git
Cloning into 'james_blog'...
remote: Enumerating objects: 96, done.
remote: Total 96 (delta 0), reused 0 (delta 0), pack-reused 96R
Receiving objects: 100% (96/96), 224.81 KiB | 339.00 KiB/s, done.
Resolving deltas: 100% (9/9), done.
PS E:\docker\jekyll> ls
1
2
3
4
5
6
7
8
9
10
11
┌──(liruilong㉿Liruilong)-[/mnt/e/docker]
└─$ sudo docker run -v /mnt/e/docker/james_blog:/data/ --name james_blog jamtur01/jekyll
Configuration file: /data/_config.yml
Source: /data
Destination: /var/www/html
Generating...
done.
Auto-regeneration: disabled. Use --watch to enable.

┌──(liruilong㉿Liruilong)-[/mnt/e/docker]
└─$

我们启动了一个叫作james blog的新容器,把本地的james blog目录作为/data/卷挂载到容器里。容器已经拿到网站的源代码,并将其构建到已编译的网站,存放到/var/www/html/目录卷是在一个或多个容器中特殊指定的目录,卷会绕过联合文件系统,为持久化数据和共享数据提供几个有用的特性。

**卷可以在容器间共享和重用**。共享卷时不一定要运行相应的容器。对卷的修改会直接在卷上反映出来。更新镜像时不会包含对卷的修改。卷会一直存在,直到没有容器使用它们。

利用卷,可以在不用提交镜像修改的情况下,向镜像里加入数据(如源代码、数据或者.其他内容),并且可以在容器间共享这些数据。卷在Docker宿主机的/var/lib/docker/volumes目录中。可以通过docker inspect命令查看某个卷的具体位置,如docker inspect-f “{( range .Mounts }}1f.}}lend}}”。

所以,如果想在另一个容器里使用/var/www/htm1/卷里编译好的网站,可以创建个新的链接到这个卷的容器,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
┌──(liruilong㉿Liruilong)-[/mnt/e/docker]
└─$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

┌──(liruilong㉿Liruilong)-[/mnt/e/docker]
└─$ sudo docker run -d -P --volumes-from james_blog jamtur01/apache
04683efb9f7bb1cb0e2729d480f62d7474917a5d21bec64a6361cef28850c565

┌──(liruilong㉿Liruilong)-[/mnt/e/docker]
└─$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
04683efb9f7b jamtur01/apache "/usr/sbin/apachectl…" 17 seconds ago Up 16 seconds 0.0.0.0:32769->80/tcp loving_chatelet

┌──(liruilong㉿Liruilong)-[/mnt/e/docker]
└─$

–volumes-from标志
–volumes-from把指定容器里的所有卷都加入新创建的容器里。这意味着, Apache容器可以访问之前创建的james-blog容器里/var/www/html卷中存放的编译后的Jekyl网站。即便james blog容器没有运行, Apache容器也可以访问这个卷。想想,这只是卷的特性之一。不过,容器本身必须存在。

1
2
3
4
5
6
7
8
9
10
11
12
┌──(liruilong㉿Liruilong)-[/mnt/e/docker/jekyll]
└─$ docker port ddbe29b5c188 80
0.0.0.0:32768

┌──(liruilong㉿Liruilong)-[/mnt/e/docker/jekyll]
└─$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS
PORTS NAMES
ddbe29b5c188 jamtur01/apache "/usr/sbin/apachectl…" 6 minutes ago Up 6 minutes 0.0.0.0:32768->80/tcp nostalgic_einstein

┌──(liruilong㉿Liruilong)-[/mnt/e/docker/jekyll]
└─$

查看一下容器把已公开的80端口映射到了哪个

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
┌──(liruilong㉿Liruilong)-[/mnt/e/docker]
└─$ docker port 04683efb9f7b 80 | xargs curl

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">

<title>Hello World!</title>

<meta name="author" content="James Turnbull">
<meta name="viewport" content="width=device-width, initial-scale=1.0">

<link href="/assets/themes/bootstrap/resources/bootstrap/css/bootstrap.min.css" rel="stylesheet">

<!--[if lt IE 9]>
<script src="/assets/themes/bootstrap/resources/respond/Respond.min.js"></script>
<![endif]-->

<link href="/atom.xml" type="application/atom+xml" rel="alternate" title="Sitewide ATOM Feed">
<link href="/rss.xml" type="application/rss+xml" rel="alternate" title="Sitewide RSS Feed">

</head>
┌──(liruilong㉿Liruilong)-[/mnt/e/docker/jekyll]
└─$

在这里插入图片描述

6.1.6 更新Jekyll网站

1
2
3
4
5
6
7
8
9
10
┌──(liruilong㉿Liruilong)-[/mnt/e/docker/james_blog]
└─$ vim _config.yml

┌──(liruilong㉿Liruilong)-[/mnt/e/docker/james_blog]
└─$ cat _config.yml | grep title
permalink: /:categories/:year/:month/:day/:title
title : 山河已无恙的 Blog

┌──(liruilong㉿Liruilong)-[/mnt/e/docker/james_blog]
└─$

在这里插入图片描述

6.1.7 备份Jekyll卷

由于卷的优点之一就是可以挂载到任意容器,因此可以轻松备份它们。现在创建一个新容器,用来备份/var/www/html卷.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
┌──(liruilong㉿Liruilong)-[/mnt/e/docker/james_blog]
└─$ sudo docker run --rm --volumes-from james_blog \
> -v $(pwd):/backup ubuntu \
> tar cvf /backup/james_blog_backup.tar /var/www/html
Unable to find image 'ubuntu:latest' locally
latest: Pulling from library/ubuntu
16ec32c2132b: Pull complete
Digest: sha256:82becede498899ec668628e7cb0ad87b6e1c371cb8a1e597d83a47fac21d6af3
Status: Downloaded newer image for ubuntu:latest
tar: Removing leading `/' from member names
/var/www/html/
/var/www/html/tags.html
/var/www/html/sitemap.txt
/var/www/html/History.markdown
/var/www/html/assets/
  • 运行了一个已有的Ubuntu容器,并把james_blog的卷挂载到该容器里。会在该容器里创建/var/www/html目录。
  • 使用-v标志把当前目录(通过$(pwd)命令获得)挂载到容器的/backup 目录。
  • 运行这一备份命令.

在这里插入图片描述

–rm标志,这个标志对于只用一次的容器,或者说用完即扔的容器,很有”用。这个标志会在容器的进程运行完毕后, 自动删除容器。

6.1.8 扩展Jekyll示例网站

  • 运行多个Apache容器,这些容器都使用来自james blog容器的卷。在这些Apache容器前面加一个负载均衡器,我们就拥有了一个Web集群。
  • 进一步构建一个镜像,这个镜像把用户提供的源数据复制(如通过git clone)到,卷里。再把这个卷挂载到从jamtur01/jeky11镜像创建的容器。这就是一个可迁移的通用方案,而且不需要宿主机本地包含任何源代码。
  • 在上一个扩展基础上可以很容易为我们的服务构建一个Web前端,这个服务用于从指定的源自动构建和部署网站。这样用户就有一个完全属于自己的GitHub Pages了。

6.2 使用Docker构建一个Java应用服务

  • 一个镜像从URL拉取指定的WAR文件并将其保存到卷里。
  • 一个含有Tomcat服务器的镜像运行这些下载的WAR文件。

6.2.1 WAR文件的获取程序

编写 Dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
13
└─$ cat Dockerfile
FROM ubuntu:18.04
LABEL maintainer="james@example.com"
ENV REFRESHED_AT 2016-06-01

RUN apt-get -qq update
RUN apt-get -qq install wget

VOLUME [ "/var/lib/tomcat8/webapps/" ]
WORKDIR /var/lib/tomcat8/webapps/

ENTRYPOINT [ "wget" ]
CMD [ "--help" ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
┌──(liruilong㉿Liruilong)-[/mnt/e/docker]
└─$ d=fetcher

┌──(liruilong㉿Liruilong)-[/mnt/e/docker]
└─$ mkdir $d;cd $d;touch Dockerfile

┌──(liruilong㉿Liruilong)-[/mnt/e/docker/fetcher]
└─$
└─$ sudo docker build -t jamtur01/fetcher .
Sending build context to Docker daemon 2.048kB
Step 1/9 : FROM ubuntu:18.04
---> 39a8cfeef173
Step 2/9 : LABEL maintainer="james@example.com"
....
Removing intermediate container ce9fd25a25e2
---> 392da3b6713a
Successfully built 392da3b6713a
Successfully tagged jamtur01/fetcher:latest

6.2.2 获取WAR文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
┌──(liruilong㉿Liruilong)-[/mnt/e/docker/fetcher]
└─$ sudo docker run -t -i --name sample jamtur01/fetcher https://tomcat.apache.org/tomcat-7.0-doc/appd
ev/sample/sample.war
--2021-08-23 12:59:56-- https://tomcat.apache.org/tomcat-7.0-doc/appdev/sample/sample.war
Resolving tomcat.apache.org (tomcat.apache.org)... 151.101.2.132, 2a04:4e42::644
Connecting to tomcat.apache.org (tomcat.apache.org)|151.101.2.132|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 4606 (4.5K)
Saving to: 'sample.war'

sample.war 100%[===================================>] 4.50K --.-KB/s in 0s

2021-08-23 12:59:58 (23.1 MB/s) - 'sample.war' saved [4606/4606]


┌──(liruilong㉿Liruilong)-[/mnt/e/docker/fetcher]
└─$

可以看到,容器通过提供的URL下载了sample.war文件。从输出结果看不出最终的保存路径,但是因为设置了容器的工作目录, sample.war文件最终会保存到/var/lib/tomcat7/webapps/目录中。可以在/var/1ib/docker目录找到这个WAR文件。

6.2.3 Tomecat8应用服务器

1
2
3
4
5
┌──(liruilong㉿Liruilong)-[/mnt/e/docker]
└─$ d=tomcat8;mkdir $d;cd $d;touch Dockerfile

┌──(liruilong㉿Liruilong)-[/mnt/e/docker/tomcat8]
└─$ vim Dockerfile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
FROM ubuntu:18.04
LABEL maintainer="james@example.com"
ENV REFRESHED_AT 2016-06-01

RUN apt-get -qq update
RUN apt-get -qq install tomcat8 default-jdk

ENV CATALINA_HOME /usr/share/tomcat8
ENV CATALINA_BASE /var/lib/tomcat8
ENV CATALINA_PID /var/run/tomcat8.pid
ENV CATALINA_SH /usr/share/tomcat8/bin/catalina.sh
ENV CATALINA_TMPDIR /tmp/tomcat8-tomcat8-tmp

RUN mkdir -p $CATALINA_TMPDIR

VOLUME [ "/var/lib/tomcat8/webapps/" ]

EXPOSE 8080

ENTRYPOINT [ "/usr/share/tomcat8/bin/catalina.sh", "run" ]
1
2
3
4
5
6
7
8
9
10
11
12
┌──(liruilong㉿Liruilong)-[/mnt/e/docker/tomcat8]
└─$ sudo docker build -t jamtur01/tomcat8 .
Sending build context to Docker daemon 2.048kB
Step 1/14 : FROM ubuntu:18.04
---> 39a8cfeef173
Step 2/14 : LABEL maintainer="james@example.com"
---> Using cache
....
---> 661bc99d59a1
Successfully built 661bc99d59a1
Successfully tagged jamtur01/tomcat8:latest

6.2.4 运行WAR文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
┌──(liruilong㉿Liruilong)-[/mnt/e/docker/tomcat8]
└─$ sudo docker run --name sample_app --volumes-from sample -d -P jamtur01/tomcat8
[sudo] password for liruilong:
41d491b0dbeeebb921872f9a3002e9732eecc93fd607dc182247527480a42bfe

┌──(liruilong㉿Liruilong)-[/mnt/e/docker/tomcat8]
└─$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
41d491b0dbee jamtur01/tomcat8 "/usr/share/tomcat8/…" 21 seconds ago Up 19 seconds 0.0.0.0:32770->8080/tcp sample_app
04683efb9f7b jamtur01/apache "/usr/sbin/apachectl…" 5 hours ago Up 5 hours 0.0.0.0:32769->80/tcp loving_chatelet

┌──(liruilong㉿Liruilong)-[/mnt/e/docker/tomcat8]
└─$ curl http://0.0.0.0:32770/sample/
<html>
<head>
<title>Sample "Hello, World" Application</title>
</head>
<body bgcolor=white>

<table border="0">

在这里插入图片描述

这会创建一个名为sample-app的容器,这个容器会复用sample容器里的卷。这意味着存储在/var/lib/tomcat7/webapps/卷里的WAR文件会从sample容器挂载到, sample_app容器,最终被Tomcat加载并执行。让我们在Web浏览器里看看这个示例程序。首先,我们必须使用docker port命令找出被公开的端口,如代码清单6-28所示。

6.2.5 基于Tomcat应用服务器的构建服务

6.3 多容器的应用栈

  • 一个Node容器,用来服务于Node应用,这个容器会链接到。
  • 一个Redis主容器,用于保存和集群化应用状态,这个容器会链接到。
  • 两个Redis副本容器,用于集群化应用状态。
  • 一个日志容器,用于捕获应用日志。

6.3.1 Node.js镜像

先从构建一个安装了Nodejs的镜像开始,这个镜像有Express应用和相应的必要的软件包,

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
┌──(liruilong㉿Liruilong)-[/mnt/e/docker/docker]
└─$ mkdir nodejs;cd nodejs;mkdir -p nodeapp;cd nodeapp
┌──(liruilong㉿Liruilong)-[/mnt/e/docker/docker/nodejs/nodeapp]
└─$ vim package.json

┌──(liruilong㉿Liruilong)-[/mnt/e/docker/docker/nodejs/nodeapp]
└─$ vim server.js

┌──(liruilong㉿Liruilong)-[/mnt/e/docker/docker/nodejs/nodeapp]
└─$ vim Dockerfile
```json
{
"name": "docker-dev",
"version": "0.1.1",
"description": "Docker Dev",
"dependencies": {
"connect-redis": "~3.4.0",
"express": ">=3.11.0",
"express-session": "~1.15.6",
"cookie-parser": "~1.4.3",
"morgan": "~1.9.1",
"hiredis": "~0.5.0",
"redis": "~0.10.3"
}
}

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
var fs = require('fs');
var express = require('express'),
session = require('express-session')
cookieParser = require('cookie-parser')
morgan = require('morgan')
app = express(),
redis = require('redis'),
RedisStore = require('connect-redis')(session),
server = require('http').createServer(app);

var logFile = fs.createWriteStream('/var/log/nodeapp/nodeapp.log', {flags: 'a'});

app.use(morgan('combined', {stream: logFile}));
app.use(cookieParser('keyboard-cat'));
app.use(session({
resave: false,
saveUninitialized: false,
store: new RedisStore({
host: process.env.REDIS_HOST || 'redis_primary',
port: process.env.REDIS_PORT || 6379,
db: process.env.REDIS_DB || 0
}),
secret: 'keyboard cat',
cookie: {
expires: false,
maxAge: 30 * 24 * 60 * 60 * 1000
}
}));

app.get('/', function(req, res) {
res.json({
status: "ok"
});
});

app.get('/hello/:name', function(req, res) {
res.json({
hello: req.params.name
});
});

var port = process.env.HTTP_PORT || 3000;
server.listen(port);
console.log('Listening on port ' + port);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
FROM ubuntu:18.04
LABEL maintainer="james@example.com"
ENV REFRESHED_AT 2016-06-01

RUN apt-get -qq update
RUN apt-get -qq install nodejs npm
RUN mkdir -p /var/log/nodeapp

ADD nodeapp /opt/nodeapp/

WORKDIR /opt/nodeapp
RUN npm install

VOLUME [ "/var/log/nodeapp" ]

EXPOSE 3000

ENTRYPOINT [ "nodejs", "server.js" ]

1
2
3
4
5
6
7
8
9
10
11
└─$ sudo docker build -t jamtur01/nodejs .
Sending build context to Docker daemon 5.632kB
Step 1/12 : FROM ubuntu:18.04
---> 39a8cfeef173
Step 2/12 : LABEL maintainer="james@example.com"
---> Using cache
......
---> 7bb107b82dc7
Successfully built 7bb107b82dc7
Successfully tagged jamtur01/nodejs:latest

6.3.2 Redis基础镜像

现在我们继续构建第一个Redis镜像:安装Redis的基础镜像。然后我们会使用这个镜像构建Redis主镜像和副本镜像。

1
2
3
4
5
┌──(liruilong㉿Liruilong)-[/mnt/e/docker/tomcat8]
└─$ mkdir redis_bash;cd redis_bash ;vim Dockerfile

┌──(liruilong㉿Liruilong)-[/mnt/e/docker/tomcat8/redis_bash]
└─$
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
FROM ubuntu:18.04
LABEL maintainer="james@example.com"
ENV REFRESHED_AT 2017-06-01

RUN apt-get -qq update
RUN apt-get install -qq software-properties-common
RUN add-apt-repository ppa:chris-lea/redis-server
RUN apt-get -qq update
RUN apt-get -qq install redis-server redis-tools

VOLUME [ "/var/lib/redis", "/var/log/redis" ]

EXPOSE 6379

CMD []

这个Redis基础镜像安装了最新版本的Redis (从PPA库安装,而不是使用Ubuntu自带的较老的Redis包),指定了两个VOLUME (/var/lib/redis和/var/log/redis),公开了Redis的默认端口6379,因为不会执行这个镜像,所以没有包含ENTRYPOINT或者CMD指令。然后我们将只是基于这个镜像构建别的镜像。

1
2
3
4
5
6
7
8
9
10
11
┌──(liruilong㉿Liruilong)-[/mnt/e/docker/tomcat8/redis_bash]
└─$ sudo docker build -t jamtu01/redis .
[sudo] password for liruilong:
Sending build context to Docker daemon 2.048kB
Step 1/11 : FROM ubuntu:18.04
---> 39a8cfeef173
....
Removing intermediate container 55c8ff1eb524
---> 9b06351c0489
Successfully built 9b06351c0489
Successfully tagged jamtu01/redis:latest

6.3.3 Redis主镜像

我们继续构建第一个Redis镜像,即Redis主服务器

1
2
3
4
5
6
7
8
9
10
┌──(liruilong㉿Liruilong)-[/mnt/e/docker/tomcat8]
└─$ d=redis_primary;mkdir $d;cd $d;vim Dockerfile

┌──(liruilong㉿Liruilong)-[/mnt/e/docker/tomcat8/redis_primary]
└─$
FROM jamtur01/redis
LABEL maintainer="james@example.com"
ENV REFRESHED_AT 2016-06-01
#关闭密码检测 指定日志位置
ENTRYPOINT [ "redis-server", "--protected-mode no", "--logfile /var/log/redis/redis-server.log" ]
1
2
3
4
5
6
7
8
9
┌──(liruilong㉿Liruilong)-[/mnt/e/docker/tomcat8/redis_primary]
└─$ sudo docker build -t jamtur01/redis_primary .
[sudo] password for liruilong:
Sending build context to Docker daemon 2.048kB
Step 1/4 : FROM jamtur01/redis
Removing intermediate container cc572817ea4e
---> 55dd53f5a2e4
Successfully built 55dd53f5a2e4
Successfully tagged jamtur01/redis_primary:latest

6.3.4 Redis副本镜像

为了配合Redis主镜像,我们会创建Redis副本镜像,保证为Node.js应用提供Redis服务的冗余度

1
2
3
4
5
6
7
8
9
10
11
12
┌──(liruilong㉿Liruilong)-[/mnt/e/docker/tomcat8/redis_primary]
└─$ d=redis_replica;mkdir $d;cd $d;touch Dockerfile

┌──(liruilong㉿Liruilong)-[/mnt/e/docker/tomcat8/redis_primary/redis_replica]
└─$ vim Dockerfile

FROM jamtur01/redis
LABEL maintainer="james@example.com"
ENV REFRESHED_AT 2016-06-01
#通过执行 SLAVEOF host port 命令,可以将当前服务器转变为指定服务器的从属服务器(slave server)。
ENTRYPOINT [ "redis-server", "--protected-mode no", "--logfile /var/log/redis/redis-replica.log", "--slaveof redis_primary 6379" ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
┌──(liruilong㉿Liruilong)-[/mnt/e/docker/tomcat8/redis_primary/redis_replica]
└─$ sudo docker build -t jamtur01/redis_replica .
Sending build context to Docker daemon 2.048kB
Step 1/4 : FROM jamtur01/redis
---> d6570236145a
Step 2/4 : LABEL maintainer="james@example.com"
---> Using cache
---> 6a4d1ac13f2b
Step 3/4 : ENV REFRESHED_AT 2016-06-01
---> Using cache
---> 124cc316623b
Step 4/4 : ENTRYPOINT [ "redis-server", "--protected-mode no", "--logfile /var/log/redis/redis-replica.log", "--slaveof redis_primary 6379" ]
---> Running in cc04596788c9
Removing intermediate container cc04596788c9
---> 2a28f368f7ab
Successfully built 2a28f368f7ab
Successfully tagged jamtur01/redis_replica:latest

┌──(liruilong㉿Liruilong)-[/mnt/e/docker/tomcat8/redis_primary/redis_replica]

6.3.5 创建Redis后端集群

现在我们已经有了Redis主镜像和副本镜像,已经可以构建我们自己的Redis复制环境了。首先我们创建一个用来运行我们的Express应用程序的网络,我们称其为express

docker查看容器的网络ip

1
2
3
4
docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' container_name_or_id
# 可直接获得容器的ip地址如:172.18.0.4
# 显示所有容器IP地址:
docker inspect --format='{{.Name}} - {{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $(docker ps -aq)

在Docker 1.9之前的版本中,不能使用Docker Networking,只能使用Docker链接来连接Redis主容器和副本容器。

1
2
3
4
5
6
7
8
9
10
11
12
## 创建express网络
┌──(liruilong㉿Liruilong)-[/mnt/e/docker/tomcat8/redis_primary/redis_replica]
└─$ sudo docker network create express
017b16936585a6cbf07dec62542f904cc662cb597355f3ad21ad3741b013f044
## 运行Redis主容器redis_primary
┌──(liruilong㉿Liruilong)-[/mnt/e/docker/redis_replica]
└─$ sudo docker run -d -h redis_primary --net express --name redis_primary jamtur01/redis_primary
a6dd74fa924f27a193be3f33ab532e92e582fc230015ac6afcaa018fcf568267

┌──(liruilong㉿Liruilong)-[/mnt/e/docker/redis_replica]
└─$ docker logs redis_primary

这里使用docker run命令从jamtur01/redis primary镜像创建了一个容器。

-h,这个标志用来设置容器的主机名。这会覆盖默认的行为(默认将容器的主机名设置为容器ID)并允许我们指定自己的主机名。使用这个标志可以确保容器使用redis-primary作为主机名,并被本地的DNS服务正确解析。

--name标志,确保容器的名字是redis primary

--net标志,确保该容器在express网络中运行。稍后我们会看到,我们将使用这个网络来保证容器连通性。

查看日志
什么日志都没有?这是怎么回事?原来Redis服务会将日志记录到一个文件而不是记录到标准输出,所以使用Docker查看不到任何日志。可以使用之前创建的/var/log/redis卷。

1
2
3
4
┌──(liruilong㉿Liruilong)-[/mnt/e/docker/redis_replica]
└─$ sudo docker logs redis_primary
┌──(liruilong㉿Liruilong)-[/mnt/e/docker/redis_replica]
└─$ sudo docker run -it --rm --volumes-from redis_primary ubuntu cat /var/log/redis/redis-server.log

在这里插入图片描述

1
2
3
4
5
6
┌──(liruilong㉿Liruilong)-[/mnt/e/docker/redis_replica]
└─$ sudo docker run -d -h redis_replical --name redis_replical --net express jamtur01/redis_replica
e580ea333671dcfc83ba473bfd622dde7758f96582fcc0ab69b3af58c912df60

┌──(liruilong㉿Liruilong)-[/mnt/e/docker/redis_replica]
└─$

这里我们运行了另一个容器:这个容器来自jamtur01/redis_replica镜像。

和之前一样,命令里指定了主机名(通过-h标志)和容器名(通过–name标志)都是redisreplical。我们还使用了–net标志在express网络中运行Redis副本容器。

1
2
┌──(liruilong㉿Liruilong)-[/mnt/e/docker/redis_replica]
└─$ sudo docker run -it --volumes-from redis_replical ubuntu cat /var/log/redis/redis-replica.log

在这里插入图片描述

第二个副本容器

1
2
3
4
5
6
┌──(liruilong㉿Liruilong)-[/mnt/e/docker/redis_bash]
└─$ sudo docker run -d -h redis_replica2 --name redis_replica2 --net express jamtur01/redis_replica
[sudo] password for liruilong:
a8342e48c9b5aa47d294682240a3e8a59a5c2cb7fb29552b8d3c58b8173923b7

┌──(liruilong㉿Liruilong)-[/mnt/e/docker/redis_bash]
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
┌──(liruilong㉿Liruilong)-[/mnt/e/docker/redis_replica]
└─$ sudo docker run -it --rm --volumes-from redis_replica2 ubuntu cat /var/log/redis/redis-replica.log
1:C 24 Aug 2021 07:57:50.780 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
1:C 24 Aug 2021 07:57:50.780 # Redis version=6.0.6, bits=64, commit=00000000, modified=0, pid=1, just started
1:C 24 Aug 2021 07:57:50.780 # Configuration loaded
1:S 24 Aug 2021 07:57:50.781 * Running mode=standalone, port=6379.
1:S 24 Aug 2021 07:57:50.781 # Server initialized
1:S 24 Aug 2021 07:57:50.781 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.
1:S 24 Aug 2021 07:57:50.782 * Ready to accept connections
1:S 24 Aug 2021 07:57:50.782 * Connecting to MASTER redis_primary:6379
1:S 24 Aug 2021 07:57:50.804 * MASTER <-> REPLICA sync started
1:S 24 Aug 2021 07:57:50.804 * Non blocking connect for SYNC fired the event.
1:S 24 Aug 2021 07:57:50.805 * Master replied to PING, replication can continue...
1:S 24 Aug 2021 07:57:50.805 * Partial resynchronization not possible (no cached master)
1:S 24 Aug 2021 07:57:50.806 * Full resync from master: 14700697ad61ad3ce1d6b36fac6b4e1403d71c9f:1428
1:S 24 Aug 2021 07:57:50.922 * MASTER <-> REPLICA sync: receiving 176 bytes from master to disk
1:S 24 Aug 2021 07:57:50.922 * MASTER <-> REPLICA sync: Flushing old data
1:S 24 Aug 2021 07:57:50.922 * MASTER <-> REPLICA sync: Loading DB in memory
1:S 24 Aug 2021 07:57:50.922 * Loading RDB produced by version 6.0.6
1:S 24 Aug 2021 07:57:50.922 * RDB age 0 seconds
1:S 24 Aug 2021 07:57:50.922 * RDB memory usage when created 1.84 Mb
1:S 24 Aug 2021 07:57:50.922 * MASTER <-> REPLICA sync: Finished with success
failed to resize tty, using default size

┌──(liruilong㉿Liruilong)-[/mnt/e/docker/redis_replica]
└─$

6.3.6创建Node容器

现在我们已经让Redis集群运行了,我们可以为启动Node.js应用启动一个容器,如代

1
2
3
4
5
6
7
8
9
10
11
12
13
┌──(liruilong㉿Liruilong)-[/mnt/e/docker/redis_bash]
└─$ sudo docker run -d --name nodeapp -p 3000:3000 --net express jamtur01/nodejs
ee0ad18cc61d5261d963924d4c496b3265420b76de8181143fa5a851e044a645

┌──(liruilong㉿Liruilong)-[/mnt/e/docker/redis_bash]
└─$ sudo docker logs nodeapp
Listening on port 3000

┌──(liruilong㉿Liruilong)-[/mnt/e/docker/redis_bash]
└─$ curl http://127.0.0.1:3000
{"status":"ok"}
┌──(liruilong㉿Liruilong)-[/mnt/e/docker/redis_bash]
└─$

6.3.7 捕获应用日志

现在应用已经可以运行了,需要把这个应用放到生产环境中。在生产环境里需要确保可以捕获日志并将日志保存到日志服务器。我们将使用Logstash“来完成这件事。我们先来创建一个Logstash镜像

1
2
3
4
5
6
7
8
┌──(liruilong㉿Liruilong)-[/mnt/e/docker]
└─$ d=logstash;mkdir $d;cd $d;vim Dockerfile

┌──(liruilong㉿Liruilong)-[/mnt/e/docker/logstash]
└─$ vim logstash.conf

┌──(liruilong㉿Liruilong)-[/mnt/e/docker/logstash]
└─$
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
FROM ubuntu:18.04
LABEL maintainer="james@example.com"
ENV REFRESHED_AT 2016-06-01

RUN apt-get -qq update
RUN apt-get -qq install wget gnupg2 openjdk-8-jdk
RUN wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | apt-key add -
RUN echo "deb https://artifacts.elastic.co/packages/5.x/apt stable main" | tee -a /etc/apt/sources.list.d/elastic-5.x.list
RUN apt-get -qq update
RUN apt-get -qq install logstash

WORKDIR /usr/share/logstash

ADD logstash.conf /usr/share/logstash/

ENTRYPOINT [ "bin/logstash" ]
CMD [ "-f", "logstash.conf", "--config.reload.automatic" ]
1
2
3
4
5
6
7
8
9
10
11
12
input {
file {
type => "syslog"
path => ["/var/log/nodeapp/nodeapp.log", "/var/log/redis/redis-server.log"]
}
}
output {
stdout {
codec => rubydebug
}
}

这个Logstash配置很简单,它监控两个文件,即/var/1og/nodeapp/nodeapp.1og/var/log/redis/redis-server.log, Logstash会一直监视这两个文件,将其中新”的内容发送给Logstash

配置文件的第二部分是output部分,接受所有Logstash输入的内容并将其输出到标准输出上。现实中,一般会将Logstash配置为输出到Elasticsearch集群或者其他的目的地,不过这里只使用标准输出做演示,所以忽略了现实的细节。

我们指定了工作目录为/opt/logstash。最后,我们指定了ENTRYPOINT为bin/.logstash,并且指定了CMD为--config=/etc/logstash.conf。这样容器启动时会启动Logstash并加载/etc/logstash.conf配置文件

构建 Logstash 镜像

1
2
3
4
5
6
7
8
9
10
11
┌──(liruilong㉿Liruilong)-[/mnt/e/docker/logstash]
└─$ sudo docker build -t jamtur01/logstash .
[sudo] password for liruilong:
Sending build context to Docker daemon 3.584kB
Step 1/13 : FROM ubuntu:18.04
---> 39a8cfeef173
Step 2/13 : LABEL maintainer="james@example.com"
Removing intermediate container 66091ab4268b
---> b473a0d43049
Successfully built b473a0d43049
Successfully tagged jamtur01/logstash:latest

启动 Logstash 容器

在这里插入图片描述

现在我们已经演示过了如何使用多个容器组成应用程序栈,演示了如何使用Docker链·接来将应用容器连在一起,还演示了如何使用Docker卷来管理应用中各种数据。这些技术可以很容易地用来构建更加复杂的应用程序和架构。

6.4 不使用SSH管理Docker容器

传统上讲,通过SSH登入运行环境或者虚拟机里来管理服务。在Docker的世界里,大部分容器都只运行一个进程,所以不能使用这种访问方法。不过就像之前多次看到的,其实不需要这种访问:可以使用卷或者链接完成大部分同样的管理操作。比如说,如果服务通过某个网络接口做管理,就可以在启动容器时公开这个接口;如果服务通过Unix套接字(socket)来管理,就可以通过卷公开这个套接字。如果需要给容器发送信号,使用docker ki11命令发送信号。

1
sudo docker kill -s <signal> <container>

为了使用nsenter,首先要拿到要进入的容器的进程ID (PID)。可以使用dockerinspect命令获得PID,如代码清单6-65所示。

1
PID=$(sudo docker inspect --format '{{t.State.Pid}}' <container>)

nsenter一般适用于Docker 1.2或者更早的版本。docker exec命令是在Docker 1.3中引入的,替换了它的大部分功能。

第七章,Docker编配和服务发现

编配(orchestration)是一个没有严格定义的概念。这个概念大概描述了自动配置、协作和管理服务过程

编配用来描述一组实践过程,管理运行在多个Docker容器里的应用,而这些Docker容器有可能运行在多个宿主机上。

Docker对编配的原生支持非常,不过整个社区围绕编配开发和集成了很多很棒的工具。·

7.1 Docker Compose,

Docker Compose,可以用一个YAML文件定义一组要启动的容器,以及容器运行时的属性。Docker Compose称这些容器为“服务”.像这样定义:

容器通过某些方法并指定一些运行时的属性来和其他容器产生交互。

7.1.1 安装 Docker Compose

1
2
sudo curl -L https://github.com/docker/compose/releases/download/1.21.2/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

或者

1
sudo pip install -U docker-compose

7.1.2获取示例应用

新建一个目录创建一个DockerFile文件

1
2
3
4
5
[root@liruilong ~]# rm -rf composeapp
[root@liruilong ~]# dir=composeapp
[root@liruilong ~]# mkdir $dir;cd $dir;touch Dockerfile
[root@liruilong composeapp]#

创建一个DockerFile文件 :

  • 应用容器,运行Python示侧程序。
  • Redis容器,运行Redis数据库。

模拟应用程序:app.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from flask import Flask
from redis import Redis
import os

app = Flask(__name__)
redis = Redis(host="redis", port=6379)

@app.route('/')
def hello():
redis.incr('hits')
return 'Hello Docker Book reader! I have been seen {0} times'.format(redis.get('hits'))

if __name__ == "__main__":
app.run(host="0.0.0.0", debug=True)

创建requirements.txt文件来保存应用程序的依赖关系

1
2
flask
redis

创建 Dockerfile 文件

1
2
3
4
5
6
7
8
9
10
11
12
#基于python:2.7镜像构建
FROM python:2.7
LABEL maintainer="james@example.com"
ENV REFRESHED_AT 2016-08-01

#添加文件app.py和. requirements.txt到镜像中的/composeapp目录
ADD . /composeapp
# 将工作目录设置为/composeapp,并执行pip命令来安装应用的依赖: flask和redis.
WORKDIR /composeapp

RUN pip install -r requirements.txt

使用 docker build 来构建镜像

1
sudo docker build composeapp .

7.1.3 docker-compose.yml文件

现在应用镜像已经构建好了,可以配置Compose来创建需要的服务了。
在Compose中,我们定义了一组要启动的服务(以Docker容器的形式表现),我们还定义了我们希望这些服务要启动的运行时属性.

这些属性和docker run命令需要的参数类似。将所有与服务有关的属性都定义在一个YAML文件里。之后执行docker-compose up命令, Compose会启动这些容器,使用指定的参数来执行,并将所有的日志输出合并到一起

1
2
touch docker-compose.yml
vim docker-compose.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
version: '3'
services:
web:
image: composeapp
command: python app.py
## 映射端口
ports:
- "5000:5000"
## 映射路径
volumes:
- .:/composeapp
redis:
image: redis

7.1.4运行Compose

1
2
3
4
5
6
7
8
9
10
11
[root@liruilong docker]# vim app.py
[root@liruilong docker]# vim docker-compose.yml
[root@liruilong docker]# docker-compose up -d
Creating docker_redis_1 ...
Creating docker_web_1 ... error

ERROR: for docker_web_1 Cannot start service web: driver failed programming external connectivity on endpoint docker_weCreating docker_redis_1 ... done
0:5000: bind: address already in use

ERROR: for web Cannot start service web: driver failed programming external connectivity on endpoint docker_web_1 (101bedf849c53b6182020e7d51a12a36e566e87f1a269e667086da7d4a1fea4a): Error starting userland proxy: listen tcp 0.0.0.0:5000: bind: address already in use
ERROR: Encountered errors while bringing up the project.

嗯,报错了端口被占用,解决一下,把占用的端口kill掉

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
[root@liruilong docker]# ps -a
PID TTY TIME CMD
13874 pts/3 00:00:00 vim
15546 pts/4 00:00:00 ps
[root@liruilong docker]# netstat -tnalp
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:8080 0.0.0.0:* LISTEN 7234/java
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 1964/sshd
tcp 0 0 0.0.0.0:2181 0.0.0.0:* LISTEN 7234/java
tcp 0 0 0.0.0.0:27017 0.0.0.0:* LISTEN 24880/mongod
tcp 0 0 0.0.0.0:6379 0.0.0.0:* LISTEN 799/redis-server *:
tcp 0 0 0.0.0.0:42445 0.0.0.0:* LISTEN 7234/java
tcp 0 36 172.17.57.70:22 39.154.13.103:1859 ESTABLISHED 13923/sshd: root@pt
tcp 0 0 172.17.57.70:54780 100.100.30.25:80 ESTABLISHED 15077/AliYunDun
tcp 0 0 172.17.57.70:59758 100.100.105.70:80 TIME_WAIT -
tcp 0 0 172.17.57.70:56292 100.100.18.120:443 TIME_WAIT -
tcp 0 0 172.17.57.70:22 39.154.13.103:2607 ESTABLISHED 13801/sshd: root@pt
tcp 0 0 172.17.57.70:59760 100.100.105.70:80 TIME_WAIT -
tcp 0 0 172.17.57.70:22 39.154.13.103:2582 ESTABLISHED 13831/sshd: root@pt
tcp 0 0 172.17.57.70:59762 100.100.105.70:80 TIME_WAIT -
tcp 0 0 172.17.57.70:59754 100.100.105.70:80 TIME_WAIT -
tcp6 0 0 :::5000 :::* LISTEN 23998/registry
tcp6 0 0 :::3306 :::* LISTEN 24962/mysqld
tcp6 0 0 :::6379 :::* LISTEN 799/redis-server *:
[root@liruilong docker]# kill 23998

在执行一次

1
2
3
4
5
6
7
8
[root@liruilong docker]# docker-compose up -d
docker_redis_1 is up-to-date
Starting docker_web_1 ... done
[root@liruilong docker]# curl http://0.0.0.0:5000/
Hello Docker Book reader! I have been seen 1 times[root@liruilong docker]#
[root@liruilong docker]# curl http://0.0.0.0:5000/
Hello Docker Book reader! I have been seen 2 times[root@liruilong docker]#
[root@liruilong docker]#

在这里插入图片描述

7.1.5使用Compose

docker-compose ps命令列出了本地docker-compose.yml文件里定义的正在运行的所有服务,可以使用docker-compose rm命令来删除这些服务,还可以使用docker-compose logs命令来进一步查看服务的日志事件,

如果使用docker-compose stop或者docker-compose ki11命令停止服务,还”可以使用docker-compose start命令重新启动这些服务。

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
[root@liruilong docker]# docker-compose ps
Name Command State Ports
--------------------------------------------------------------------------------
docker_redis_1 docker-entrypoint.sh redis ... Up 6379/tcp
docker_web_1 python app.py Up 0.0.0.0:5000->5000/tcp
[root@liruilong docker]# docker-compose logs
Attaching to docker_web_1, docker_redis_1
web_1 | * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
web_1 | * Restarting with stat
web_1 | * Debugger is active!
web_1 | * Debugger PIN: 212-073-889
web_1 | 172.19.0.1 - - [21/Aug/2021 17:59:57] "GET / HTTP/1.1" 200 -
web_1 | 172.19.0.1 - - [21/Aug/2021 18:00:02] "GET / HTTP/1.1" 200 -
redis_1 | 1:C 21 Aug 2021 17:57:52.123 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
redis_1 | 1:C 21 Aug 2021 17:57:52.123 # Redis version=6.2.5, bits=64, commit=00000000, modified=0, pid=1, just started
redis_1 | 1:C 21 Aug 2021 17:57:52.123 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
redis_1 | 1:M 21 Aug 2021 17:57:52.126 * monotonic clock: POSIX clock_gettime
redis_1 | 1:M 21 Aug 2021 17:57:52.127 * Running mode=standalone, port=6379.
redis_1 | 1:M 21 Aug 2021 17:57:52.127 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
redis_1 | 1:M 21 Aug 2021 17:57:52.127 # Server initialized
redis_1 | 1:M 21 Aug 2021 17:57:52.127 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
redis_1 | 1:M 21 Aug 2021 17:57:52.127 * Ready to accept connections
[root@liruilong docker]# docker-compose stop;docker-compose kill;
Stopping docker_web_1 ... done
Stopping docker_redis_1 ... done
[root@liruilong docker]# docker-compose ps
Name Command State Ports
----------------------------------------------------------------
docker_redis_1 docker-entrypoint.sh redis ... Exit 0
docker_web_1 python app.py Exit 0
[root@liruilong docker]# docker-compose rm
Going to remove docker_web_1, docker_redis_1
Are you sure? [yN] y
Removing docker_web_1 ... done
Removing docker_redis_1 ... done
[root@liruilong docker]# docker-compose ps
Name Command State Ports
------------------------------
[root@liruilong docker]#

7.1.6 Compose小结

可以将Compose与提供图形化用户界面的Shipyard 一起使用。

https://docs.docker.com/compose/rails/
https://docs.docker.com/compose/diango/
https://docs.docker.com/compose/wordpress/)
https://github.com/shipyard/shipyard
https://dos.docker.com/compose/cli/

7.2 Consul,服务发现和Docker

服务发现分布式应用程序之间管理相互关系的一种机制。一个分布式程序一般由多个

7.3 Docker Swarm

7.4其他编配工具和组件

编配工具是一个快速发展的生态环境,没有办法列出这个领域中的所有可用的工具。这些工具·的功能不尽相同,不过大部分都属于以下两个类型;

  • 调度和集群管理;
  • 服务发现。

7.4.1 Fleet和etco

7.4.2 Kubernetes

7.4.3 Apache Mesos

7.4.4 Helios

7.4.5 Centurion

发布于

2021-07-22

更新于

2023-06-21

许可协议

评论
加载中,最新评论有1分钟缓存...
Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×