《第一本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 | [root@liruilong ~]# docker info |
3.2运行我们的第一个容器
1 | [root@liruilong ~]# docker images |
-i
标志保证容器中STDIN是开启的,尽管我们并没有附着到容器中。持久的标准输入是交互式shell的半边天。-t
标志则是另外,它docker为要创建的容器分配·一个伪tty终端。这样,新创建的容器才能提供一个交互式shell.若要在命令行下创建一个,我们能与之进行交互的容器,而不是一个运行后台服务的容器,则这两个参数已经是最基本的参数了。
Docker会检查本地是否是存在镜像,如果本地还没有该镜像的话,那么Docker就会连接官方维护的DotrHub Rggistry,查看Docker Hub中是否有该镜像
。Docker一旦找到该镜像,就下载镜像并将共保存到本地宿主机中。
Docker在文件系统内部用这个镜像创全新容器。该容器拥有自己的网络IP地址/以及一个用来和宿主机进行通信 桥接网络接口。最后,我们告诉Docker在新容器中要运行什么命令,在本例中我们在容器行/bin/bash命令启动了一个Bash shell.
3.3使用第一个容器
1 | [root@liruilong ~]# docker run -i -t e041132b8b32 /bin/bash |
用户可以继续在容器中做任何自己想做的事情。当所有工作都结束时,输入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 |
|
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 | ┌──(liruilong㉿Liruilong)-[/mnt/e/docker/uag/uag_jdk] |
3.14自动重启容器
1 | ┌──(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 | ┌──(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
镜像是由文件系统叠加而成,底端是一个引导文件系统 bootfs
。Docker
用户几乎永远不会和引导文件交互。实际上,当一个容器启动.后,它将会被移到内存中,而引导文件系统则会被卸载(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 | FROM ubuntu:18.04 |
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 | FROM ubuntu:18.04 |
分析一下这个新的
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 | ENTRYPOINT [ "/usr/sbin/nginx" ] |
使用ENTYRPOINT
1 | ENTRYPOINT [ "usr/sbin/nginx" ] |
同时使用ENTRYPOINT和CMD
1 | ENTRYPOINT [ "/usr/sbin/nginx" ] |
如果确实需要,用户也可以在运行时通过
docker run的--entrypoint
标志覆盖ENTRYPOINT
指令。
3. WORKDIR
WORKDIR
指令用来在从镜像创建一个新容器
时,在容器内部设置一个工作目录,ENTRYPOINT和/或CMD指定的程序会在这个目录下执行
。我们可以使用该指令为Dockerfile中后续的一系列指令设置工作目录,也可以为最终的容器设置工作目录
。
1 | WORKDIR /opt/webapp/db |
这里,我们将工作目录切换为/opt/webapp/db后运行了bundle install命令,之后又将工作目录设置为/opt/webapp,最后设置了ENTRYPOINT指令来启动rackup命令。
可以通过-w标志在运行时覆盖工作目录
.
1 | sudo docker run -it -w /var/log ubuntu pwd |
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 | USER user |
如果不通过USER指令指定用户,默认用户为root
6. VOLUME
VOLUME指令用来向基于镜像创建的容器添加卷。一个卷是可以存在于一个或者多个容器内的特定的目录,这个目录可以绕过联合文件系统,并提供如下共享数据或者对数据进行持久化的功能。
- 卷可以在容器间共享和重用。
- 一个容器可以不是必须和其他容器共享卷。
- 对卷的修改是立时生效的。
- 对卷的修改不会对更新镜像产生影响。
- 卷会一直存在直到没有任何容器再使用它。
**卷功能让我们可以将数据(如源代码)、数据库或者其他内容添加到镜像中而不是将这些内容提交到镜像中,并且允许我们在多个容器间共享这些内容。我们可以利用此功能来测·试容器和内部的应用程序代码,管理日志,或者处理容器内部的数据库**。
1 | VOLUME [ "/opt/project" ] |
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 | ┌──(liruilong㉿Liruilong)-[/mnt/e/docker/uag/uag_nginx] |
1 | docker inspect nginx_log // |
10. STOPSIGNAL
**STOPSIGNAL指令用来设置停止容器时发送什么系统调用信号给容器**。这个信号必须·是内核系统调用表中合法的数,如9,或者SIGNAME格式中的信号名称,如SIGKILL注意STOPSIGNAL指令是在Docker 1.9版本中引入的。
11. ARG
**ARG
指令用来定义可以在docker build
命令运行时传递给构建运行时的变量
,**我们只需要在构建时使用--build-arg
标志即可。用户只能在构建时指定在Dockerfile文件
中定义过的参数。
1 | ARG build |
1 | docker build --build-arg build=1234 -t jamtur01/webapp . |
ARG指令是在Docker 1.9版本中引入的,Docker预定义了一组ARG变量,可以在构建时直接使用,而不必再到Dockerfile中自行定义。
1 | HTTP_PROXY |
12. ONBUIID
ONBUILD
指令能为镜像添加触发器(trigger)
。**当一个镜像被用做其他镜像的基础镜像时(比如用户的镜像需要从某未准备好的位置添加源代码,或者用户需要执行特定于构建镜像的环境的构建脚本),该镜像中的触发器将会被执行**。触发器会在构建过程中插入新指令
,我们可以认为这些指令是紧跟在FROM之后指定
的。触发器可以是任何构建指令,
1 | ONBUILD ADD . /app/src |
上面的代码将会在创建的镜像中加入ONBUILD触发器, ONBUILD指令可以在镜像上行docker inspect命令来查看,+
4.6将镜像推送到DockerHub
嗯,需要注册一个Docker Hub账号
,然后登录,需要镜像前面加 账户名/
1 | ┌──(liruilong㉿Liruilong)-[/mnt/e/docker/uag/uag_nginx] |
4.7删除镜像
1 | docker rmi 镜像名 |
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 | docker images |
1 | ┌──(liruilong㉿Liruilong)-[/mnt/e/docker/uag/uag_nginx] |
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 | server { |
nginx.conf
1 | user www-data; |
我们还需要将Nginx配置为非守护进程的模式,这样可以让Nginx在Docker容器里工作。
网站测试的基本Dockerfile
1 | FROM ubuntu:18.04 |
这个简单的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 | ┌──(liruilong㉿Liruilong)-[/mnt/e/docker/logstash/sample] |
5.1.3从Sample网站和Nainx镜像构建容器
1 | ┌──(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 | FROM ubuntu:14.04 |
5.2.2 创建Sinatra容器
下载web应用程序
1 | ┌──(liruilong㉿Liruilong)-[/mnt/e/docker/sinatra] |
启动一个Sinatra容器
1 | sudo docker run -d -p 4567 --name webapp -v $PWD/webapp:/opt/webapp jamtur01/sinatra |
查看日志,进程:
1 | docker logs webapp |
嗯,这个不知道啥原因,这个镜像没有拉下来,所以这部分没实际的搞,但是应该很简单。
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 NetworkingDocker链接(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 foreverDocker每
创建
一个容器
就会创建一组互联的网络接口
。这组接口就像管道的两端
(就是说,从一端发送的数据会在另一端接收到)。这组接口其中一端作为容器里的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 | [root@badb10b3f287 /]# yum update && yum install -y traceroute |
不过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 | docker network create NetWorkUag |
除了运行于单个主机之上的桥接网络,我们也可以创建一个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 | ┌──(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 | FROM ubuntu:18.04 |
- 镜像基于Ubuntu 14.04,并且安装了Ruby和用于支持Jekyll的包。然后我们使用VOLUME指令创建了以下两个卷。,/data/,用来存放网站的源代码。./var/www/htm1/,用来存放编译后的Jekyll网站码然后我们需要将工作目录设置到/data/,并通过ENTRYPOINT指令指定自动构建的命令,这个命令会将工作目录/data/中的所有的Jekyll网站代码构建到/var/www/html/目录中。
6.1.2 构建Jekyl基础镜像
1 | PS E:\docker> bash |
6.1.3 Apache镜像
构建 apache Dockerfile文件
1 | RUN apt-get -qq update |
- 这个镜像也是基于Ubuntu 14.04的,并安装了Apache。然后我们使用VOLUME指令创建了一个卷,即/var/www/html/,用来存放编译后的Jekyll网站。
- 将/var/www/html设为工作目录。然后我们使用ENV指令设置了一些必要的环境变量,创建了必要的目录,并且使用EXPOSE公开了80端口。最后指定了ENTRYPOINT和CMD指令组合来在容器启动时默认运行Apache
6.1.4 构建Jekyll Apache镜像
1 | ┌──(liruilong㉿Liruilong)-[/mnt/e/docker/apache] |
6.1.5 启动Jekyll网站
1 | PS E:\docker\jekyll> git clone git@github.com:turnbullpress/james_blog.git |
1 | ┌──(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 | ┌──(liruilong㉿Liruilong)-[/mnt/e/docker] |
–volumes-from标志
–volumes-from把指定容器里的所有卷都加入新创建的容器里。这意味着, Apache容器可以访问之前创建的james-blog容器里/var/www/html卷中存放的编译后的Jekyl网站。即便james blog容器没有运行, Apache容器也可以访问这个卷。想想,这只是卷的特性之一。不过,容器本身必须存在。
1 | ┌──(liruilong㉿Liruilong)-[/mnt/e/docker/jekyll] |
查看一下容器把已公开的80端口映射到了哪个
1 | ┌──(liruilong㉿Liruilong)-[/mnt/e/docker] |
6.1.6 更新Jekyll网站
1 | ┌──(liruilong㉿Liruilong)-[/mnt/e/docker/james_blog] |
6.1.7 备份Jekyll卷
由于卷的优点之一就是可以挂载到任意容器
,因此可以轻松备份它们。现在创建一个新容器,用来备份/var/www/html卷.
1 | ┌──(liruilong㉿Liruilong)-[/mnt/e/docker/james_blog] |
- 运行了一个已有的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 | └─$ cat Dockerfile |
1 | ┌──(liruilong㉿Liruilong)-[/mnt/e/docker] |
6.2.2 获取WAR文件
1 | ┌──(liruilong㉿Liruilong)-[/mnt/e/docker/fetcher] |
可以看到,容器通过提供的URL下载了sample.war文件。从输出结果看不出最终的保存路径,但是因为设置了容器的工作目录, sample.war文件最终会保存到/var/lib/tomcat7/webapps/目录中
。可以在/var/1ib/docker目录找到这个WAR文件。
6.2.3 Tomecat8应用服务器
1 | ┌──(liruilong㉿Liruilong)-[/mnt/e/docker] |
1 | FROM ubuntu:18.04 |
1 | ┌──(liruilong㉿Liruilong)-[/mnt/e/docker/tomcat8] |
6.2.4 运行WAR文件
1 | ┌──(liruilong㉿Liruilong)-[/mnt/e/docker/tomcat8] |
这会创建一个名为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 | ┌──(liruilong㉿Liruilong)-[/mnt/e/docker/docker] |
1 | var fs = require('fs'); |
1 | FROM ubuntu:18.04 |
1 | └─$ sudo docker build -t jamtur01/nodejs . |
6.3.2 Redis基础镜像
现在我们继续构建第一个Redis镜像:安装Redis的基础镜像
。然后我们会使用这个镜像构建Redis主镜像和副本镜像。
1 | ┌──(liruilong㉿Liruilong)-[/mnt/e/docker/tomcat8] |
1 | FROM ubuntu:18.04 |
这个Redis基础镜像安装了最新版本的Redis (从PPA库安装,而不是使用Ubuntu自带的较老的Redis包),指定了两个VOLUME (/var/lib/redis和/var/log/redis),公开了Redis的默认端口6379,因为不会执行这个镜像,所以没有包含ENTRYPOINT或者CMD指令
。然后我们将只是基于这个镜像构建别的镜像。
1 | ┌──(liruilong㉿Liruilong)-[/mnt/e/docker/tomcat8/redis_bash] |
6.3.3 Redis主镜像
我们继续构建第一个Redis镜像,即Redis主服务器
1 | ┌──(liruilong㉿Liruilong)-[/mnt/e/docker/tomcat8] |
1 | ┌──(liruilong㉿Liruilong)-[/mnt/e/docker/tomcat8/redis_primary] |
6.3.4 Redis副本镜像
为了配合Redis主镜像,我们会创建Redis副本镜像,保证为Node.js应用提供Redis服务的冗余度
1 | ┌──(liruilong㉿Liruilong)-[/mnt/e/docker/tomcat8/redis_primary] |
1 | ┌──(liruilong㉿Liruilong)-[/mnt/e/docker/tomcat8/redis_primary/redis_replica] |
6.3.5 创建Redis后端集群
现在我们已经有了Redis主镜像和副本镜像
,已经可以构建我们自己的Redis复制环境
了。首先我们创建一个用来运行我们的Express应用程序的网络,我们称其为express
docker查看容器的网络ip
1 | docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' container_name_or_id |
在Docker 1.9之前的版本中,不能使用Docker Networking,只能使用Docker链接来连接Redis主容器和副本容器。
1 | ## 创建express网络 |
这里使用docker run命令从jamtur01/redis primary镜像创建了一个容器。
-h,这个标志用来设置容器的主机名
。这会覆盖默认的行为(默认将容器的主机名设置为容器ID)并允许我们指定自己的主机名。使用这个标志可以确保容器使用redis-primary作为主机名,并被本地的DNS服务正确解析。
--name标志,确保容器的名字是redis primary
--net标志,确保该容器在express网络中运行
。稍后我们会看到,我们将使用这个网络来保证容器连通性。
查看日志
什么日志都没有?这是怎么回事?原来Redis服务会将日志记录到一个文件而不是记录到标准输出,所以使用Docker查看不到任何日志。可以使用之前创建的/var/log/redis卷。
1 | ┌──(liruilong㉿Liruilong)-[/mnt/e/docker/redis_replica] |
1 | ┌──(liruilong㉿Liruilong)-[/mnt/e/docker/redis_replica] |
这里我们运行了另一个容器:这个容器来自jamtur01/redis_replica镜像。
和之前一样,命令里指定了主机名(通过-h标志)和容器名(通过–name标志)都是redisreplical。我们还使用了–net标志在express网络中运行Redis副本容器。
1 | ┌──(liruilong㉿Liruilong)-[/mnt/e/docker/redis_replica] |
第二个副本容器
1 | ┌──(liruilong㉿Liruilong)-[/mnt/e/docker/redis_bash] |
1 | ┌──(liruilong㉿Liruilong)-[/mnt/e/docker/redis_replica] |
6.3.6创建Node容器
现在我们已经让Redis集群运行了,我们可以为启动Node.js应用启动一个容器,如代
1 | ┌──(liruilong㉿Liruilong)-[/mnt/e/docker/redis_bash] |
6.3.7 捕获应用日志
现在应用已经可以运行了,需要把这个应用放到生产环境中。在生产环境里需要确保可以捕获日志并将日志保存到日志服务器。我们将使用Logstash
“来完成这件事。我们先来创建一个Logstash镜像
。
1 | ┌──(liruilong㉿Liruilong)-[/mnt/e/docker] |
1 | FROM ubuntu:18.04 |
1 | input { |
这个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 | ┌──(liruilong㉿Liruilong)-[/mnt/e/docker/logstash] |
启动 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 | 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 |
或者
1 | sudo pip install -U docker-compose |
7.1.2获取示例应用
新建一个目录创建一个DockerFile文件
1 | [root@liruilong ~]# rm -rf composeapp |
创建一个DockerFile文件 :
- 应用容器,运行Python示侧程序。
- Redis容器,运行Redis数据库。
模拟应用程序:app.py
1 | from flask import Flask |
创建requirements.txt
文件来保存应用程序的依赖关系
1 | flask |
创建 Dockerfile 文件
1 | #基于python:2.7镜像构建 |
使用 docker build 来构建镜像
1 | sudo docker build composeapp . |
7.1.3 docker-compose.yml文件
现在应用镜像已经构建好了,可以配置Compose来创建需要的服务了。
在Compose中,我们定义了一组要启动的服务(以Docker容器的形式表现),我们还定义了我们希望这些服务要启动的运行时属性.
这些属性和docker run
命令需要的参数
类似。将所有与服务有关的属性
都定义在一个YAML
文件里。之后执行docker-compose up
命令, Compose
会启动这些容器,使用指定的参数来执行
,并将所有的日志输出合并到一起
。
1 | touch docker-compose.yml |
1 | version: '3' |
7.1.4运行Compose
1 | [root@liruilong docker]# vim app.py |
嗯,报错了端口被占用,解决一下,把占用的端口kill掉
1 | [root@liruilong docker]# ps -a |
在执行一次
1 | [root@liruilong docker]# docker-compose up -d |
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 | [root@liruilong docker]# docker-compose ps |
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
《第一本Docker书》 读书笔记
https://liruilongs.github.io/2021/07/22/docker/《第一本DOCKER书》 读书笔记/