一杯茶的时间,上手 Docker

镜像与容器:筑梦师的图纸和梦境


镜像(Image)和容器(Container)是 Docker 中最为基础也是最为关键的两个概念,前者就是筑梦师的图纸,根据这张图纸的内容,就能够生成完全可预测的梦境(也就是后者)。

提示

如果你觉得这个比喻难以理解,那么可以通过面向对象编程中“类”(class)和“实例”(instance)这两个概念进行类比,“类”就相当于“镜像”,“实例”就相当于“容器”。

小试牛刀:梦开始的地方

在略微接触了镜像与容器这两个基础概念之后,我们打算暂停理论的讲解,而先来一波小实验让你快速感受一下。

实验一:Hello World!

按照历史惯例,我们运行一下来自 Docker 的 Hello World,命令如下:

docker run hello-world

输出如下:

Unable to find image ‘hello-world:latest’ locally

latest: Pulling from library/hello-world

1b930d010525: Pull complete

Digest: sha256:fb158b7ad66f4d58aa66c4455858230cd2eab4cdf29b13e5c3628a6bfc2e9f05

Status: Downloaded newer image for hello-world:latest

Hello from Docker!

不就打印了一个字符串然后退出吗,有这么神奇?其实 Docker 为我们默默做了以下事情:

1.检查本地是否有指定的 hello-world:latest 镜像(latest 是镜像标签,后面会细讲),如果没有,执行第 2 步,否则直接执行第 3 步2.本地没有指定镜像(Unable to find xxx locally),从 Docker Hub[4] 下载到本地3.根据本地的 hello-world:latest 镜像创建一个新的容器并运行其中的程序4.运行完毕后,容器退出,控制权返回给用户

实验二:运行一个 Nginx 服务器

感觉太简单?我们来尝试一个高级一点的:运行一个 Nginx 服务器[5]。运行以下命令

docker run -p 8080:80 nginx

运行之后,你会发现一直卡住,也没有任何输出,但放心你的电脑并没有死机。让我们打开浏览器访问 localhost:8080

这时候熟悉 Nginx 的朋友可能就坐不住了:就一个简简单单的 docker run 命令,就搞定了 Nginx 服务器的安装和部署??没错,你可以继续访问一些不存在的路由,比如 localhost:8080/what,同样会提示 404。这时候我们再看 Docker 容器的输出,就有内容(服务器日志)了:

总结一下刚才 Docker 做的事情:

1.检查本地是否有指定的 nginx:latest 镜像(关于 latest 标签,后面会细讲),如果没有,执行第 2 步,否则直接执行第 3 步2.本地没有指定镜像(Unable to find xxx locally),从 Docker Hub[6] 下载到本地3.根据本地的 nginx:latest 镜像创建一个新的容器,并通过 -p--publish)参数建立本机的 8080 端口与容器的 80 端口之间的映射,然后运行其中的程序4.Nginx 服务器程序保持运行,容器也不会退出

提示

端口映射规则的格式为 <本机端口>:<容器端口>。Nginx 容器默认开放了 80 端口,我们通过设置 8080:80 的端口映射规则,就可以在本机(容器之外)通过访问 localhost:8080 访问,甚至可以在同一局域网内通过内网 IP 访问,这篇文章的最后会演示哦。

实验三:后台运行 Nginx

看上去很酷,不过像 Nginx 服务器这样的进程我们更希望把它抛到后台一直运行。按 Ctrl + C 退出当前的容器,然后再次运行以下命令:

docker run -p 8080:80 --name my-nginx -d nginx

注意到与之前不同的是,我们:

•加了一个参数 --name,用于指定容器名称为 my-nginx•加了一个选项 -d--detach),表示“后台运行”

警告

容器的名称必须是唯一的,如果已经存在同一名称的容器(即使已经不再运行)就会创建失败。如果遇到这种情况,可以删除之前不需要的容器(后面会讲解怎么删除)。

Docker 会输出一串长长的 64 位容器 ID,然后把终端的控制权返回给了我们。我们试着访问 localhost:8080,还能看到那一串熟悉的 Welcome to nginx!,说明服务器真的在后台运行起来了。

那我们怎么管理这个服务器呢?就像熟悉的 UNIX ps 命令一样,docker ps 命令可以让我们查看当前容器的状态:

docker ps

输出结果是这样的:

提示

由于 docker ps 的输出比较宽,如果你觉得结果不直观的话可以把终端(命令行)拉长,如下图所示:

从这张表中,就可以清晰地看到了我们在后台运行的 Nginx 服务器容器的一些信息:

•容器 ID(Container ID)为 0bddac16b8d8(你机器上的可能不一样)•所用镜像(Image)为 nginx•运行命令/程序(Command)为 nginx -g 'daemon of...,这个是 Nginx 镜像自带的运行命令,暂时不用关心•创建时间(Created)为 45 seconds ago(45 秒钟之前)•当前状态(Status)为 Up 44 seconds(已运行 44 秒钟)•端口(Ports)为 0.0.0.0:8080->80/tcp,意思是访问本机的 0.0.0.0:8080 的所有请求会被转发到该容器的 TCP 80 端口•名称(Names)为刚才指定的 my-nginx

如果我们要让容器停下来,通过 docker stop 命令指定容器名称或 ID 进行操作即可,命令如下:

docker stop my-nginx

docker stop 0bddac16b8d8

注意

如果指定容器 ID 的话,记得要换成自己机器上真实的 ID 哦。此外,在没有冲突的情况下,ID 可以只写前几位字符,例如写 0bd 也是可以的。

实验四:交互式运行

在过了一把 Nginx 服务器的瘾之后,我们再来体验一下 Docker 容器的另一种打开方式:交互式运行。运行以下命令,让我们进入到一个 Ubuntu 镜像中:

docker run -it --name dreamland ubuntu

可以看到我们加了 -it 选项,等于是同时指定 -i--interactive,交互式模式)和 -t--tty,分配一个模拟终端) 两个选项。以上命令的输出如下:

Unable to find image ‘ubuntu:latest’ locally

latest: Pulling from library/ubuntu

2746a4a261c9: Pull complete

4c1d20cdee96: Pull complete

0d3160e1d0de: Pull complete

c8e37668deea: Pull complete

Digest: sha256:9207fc49baba2e62841d610598cb2d3107ada610acd4f47252faf73ed4026480

Status: Downloaded newer image for ubuntu:latest

root@94279dbf5d93:/#

等下,我们怎么被抛在了一个新的命令行里面?没错,你现在已经在这个 Ubuntu 镜像构筑的“梦境”之中,你可以随意地“游走”,运行一些命令:

root@94279dbf5d93:/# whoami

root

root@94279dbf5d93:/# ls

bin dev home lib64 mnt proc run srv tmp var

boot etc lib media opt root sbin sys usr

例如我们在上面运行了 whoami 和 ls 命令,你基本上可以确定现在已经在“梦境”(容器)之中了。这时候打开一个新的终端(命令行),运行 docker ps 命令,就可以看到正在运行中的 Ubuntu 镜像:

回到之前的容器中,按 Ctrl + D (或者输入 exit 命令)即可退出。你可以在之前查看 docker ps 的终端再次检查容器是否已经被关闭了。

销毁容器:听梦碎的声音

筑梦师难免会有失败的作品,而我们刚才创建的 Docker 容器也只是用于初步探索,后续不会再用到。由于 Docker 容器是直接存储在我们本地硬盘上的,及时清理容器也能够让我们的硬盘压力小一些。我们可以通过以下命令查看所有容器(包括已经停止的):

docker ps -a

-a--all)用于显示所有容器,如果不加的话只会显示运行中的容器。可以看到输出如下(这里我把终端拉宽了,方便你看):

提示

你也许观察到,之前的实验一和实验二中我们没有指定容器名称,Docker 为我们取了颇为有趣的默认容器名称(比如 hardcore_nash),格式是一个随机的形容词加上一位著名科学家/程序员的姓氏(运气好的话,你可能会看到 Linux 之父 torvalds 哦)。

类似 Shell 中的 rm 命令,我们可以通过 docker rm 命令销毁容器,例如删除我们之前创建的 dreamland 容器:

docker rm dreamland

或者指定容器 ID,记得替换成自己机器上的

docker rm 94279dbf5d93

但如果我们想要销毁所有容器怎么办?一次次输入 docker rm 删除显然不方便,可以通过以下命令轻松删除所有容器

docker rm $(docker ps -aq)

docker ps -aq 会输出所有容器的 ID,然后作为参数传给 docker rm 命令,就可以根据 ID 删除所有容器啦。

危险!

执行之前一定要仔细检查是否还有有价值的容器(特别是业务数据),因为容器一旦删除无法再找回(这里不讨论硬盘恢复这种黑科技)!

回忆与升华

关于端口映射

可能有些同学还是没有完全理解“端口映射”的概念,以 8080:80 这一条映射规则为例,我们可以用“传送门”的比喻来理解(下面的图是《传送门2》游戏的封面):

还是把容器比作“梦境”,把本机环境比作“现实”,通过建立端口映射,访问本机的 8080 端口的请求就会被“传送”到容器的 80 端口,是不是很神奇呢。

容器生命周期:梦境地图

跟着做完上面四个小实验之后,你或许已经对 Docker 容器有了非常直观的感受和理解了。是时候祭出这张十(sang)分(xin)经(bing)典(kuang)的 Docker 容器生命周期图了(来源:https://docker-saigon.github.io/post/Docker-Internals/):

这张图乍一看颇具视觉冲击力,甚至会让你感觉不知所措。没事,我们大致地解读这张图里面的四类元素:

1.容器状态(带颜色的圆圈):包括已创建(Created)、运行中(Running)、已暂停(Paused)、已停止(Stopped)以及被删除(Deleted)2.Docker 命令(箭头上以 docker 开头的文字):包括 docker rundocker createdocker stop 等等3.事件(矩形框):包括 createstartdiestop 还有 OOM(内存耗尽)等等4.还有一个条件判断,根据重启策略(Restart Policy)判断是否需要重新启动容器

OK,这张图还是很难一下子理解,不过还记得刚才我们做的四个小实验吗?我们实际上走了一共两条路径(也是日常使用中走的最多的路),接下来将一一进行分析。

第一条路径(自然结束)

如上图所示:

•我们先通过 docker run 命令,直接创建(create)并启动(start)一个容器,进入到运行状态(Running)•然后程序运行结束(例如输出 Hello World 之后,或者通过 Ctrl + C 使得程序终止),容器死亡(die)•由于我们没有设置重启策略,所以直接进入到停止状态(Stopped)•最后通过 docker rm 命令销毁容器,进入到被删除状态(Deleted)

第二条路径(强制结束)

•我们还是通过 docker run 命令,直接创建(create)并启动(start)一个容器,进入到运行状态(Running)•然后通过 docker stop 命令杀死容器中的程序(die)并停止(stop)容器,最终进入到停止状态(Stopped)•最后通过 docker rm 命令销毁容器,进入到被删除状态(Deleted)

提示

有些眼尖的读者可能发现 docker kill 和 docker stop 的功能非常相似,它们之前存在细微的区别: kill 命令向容器内运行的程序直接发出 SIGKILL 信号(或其他指定信号),而 stop 则是先发出 SIGTERM 再发出 SIGKILL 信号,属于优雅关闭(Graceful Shutdown)。

一条捷径:删除运行中的容器

生命周期图其实有一条捷径没有画出来:直接从运行中(或暂停中)到被删除,通过给 docker rm 命令加上选项 -f--force,强制执行)就可以实现:

假设 dreamland 还在运行中

docker rm -f dreamland

同样地,我们可以删除所有容器,无论处于什么状态:

docker rm -f $(docker ps -aq)

自由探索

你尽可以自由探索其他我们没走过的路线,例如尝试再次启动之前已经停止的容器(docker start),或者暂停正在运行的容器(docker pause)。幸运的是,docker help 命令可以为我们提供探索的指南针,例如我们想了解 start 命令的使用方法:

$ docker help start

Usage: docker start [OPTIONS] CONTAINER [CONTAINER…]

Start one or more stopped containers

Options:

-a, --attach Attach STDOUT/STDERR and forward signals

–checkpoint string Restore from this checkpoint

–checkpoint-dir string Use a custom checkpoint storage directory

–detach-keys string Override the key sequence for

detaching a container

-i, --interactive Attach container’s STDIN

读到这里,相信你已经了解了如何利用现有的镜像创造容器,并进行管理。在接下来,我们将带你创建自己的 Docker 镜像,开始成为一名标准的“筑梦师”!

容器化第一个应用:开启筑梦之旅


在之前的步骤中,我们体验了别人为我们提前准备好的镜像(例如 hello-worldnginx 和 ubuntu),这些镜像都可以在 Docker Hub[7] 镜像仓库中找到。在这一步,我们将开始筑梦之旅:学习如何容器化(Containerization)你的应用。

正如开头所说,我们将容器化一个全栈的”梦想清单“应用,运行以下命令来获取代码,然后进入项目:

git clone -b start-point https://github.com/tuture-dev/docker-dream.git

cd docker-dream

在这一步中,我们将容器化这个用 React 编写的前端应用,用 Nginx 来提供前端页面的访问。

什么是容器化

容器化包括三个阶段:

编写代码:我们已经提供了写好的代码•构建镜像:也就是这一节的核心内容,下面会详细展开•创建和运行容器:通过容器的方式运行我们的应用

构建镜像

构建 Docker 镜像主要包括两种方式:

1.手动:根据现有的镜像创建并运行一个容器,进入其中进行修改,然后运行 docker commit 命令根据修改后的容器创建新的镜像2.自动:创建 Dockerfile 文件,指定构建镜像的命令,然后通过 docker build 命令直接创建镜像

由于篇幅有限,这篇文章只会讲解使用最为广泛的第二种创建镜像的方式。

一些准备工作

我们先把前端项目 client 构建成一个静态页面。确保你的机器上已经安装 Node 和 npm(点击这里[8]下载,或使用 nvm[9]),然后进入到 client 目录下,安装所有依赖,并构建项目:

cd client

npm install

npm run build

等待一阵子后,你应该可以看到 client/build 目录,存放了我们要展示的前端静态页面。

创建 Nginx 配置文件 client/config/nginx.conf,代码如下:

server {

listen 80;

root /www;

index index.html;

sendfile on;

sendfile_max_chunk 1M;

tcp_nopush on;

gzip_static on;

location / {

try_files $uri $uri/ /index.html;

}

}

不熟悉 Nginx 配置的同学不用担心哦,直接复制粘贴就可以了。上面的配置大致意思是:监听 80 端口,网页根目录在 /www,首页文件是 index.html,如果访问 / 则提供文件 index.html

创建 Dockerfile

然后就是这一步骤中最重要的代码:Dockerfile!创建 client/Dockerfile 文件,代码如下:

FROM nginx:1.13

删除 Nginx 的默认配置

RUN rm /etc/nginx/conf.d/default.conf

添加自定义 Nginx 配置

COPY config/nginx.conf /etc/nginx/conf.d/

将前端静态文件拷贝到容器的 /www 目录下

COPY build /www

可以看到我们用了 Dockerfile 中的三个指令:

FROM 用于指定基础镜像,这里我们基于 nginx:1.13 镜像作为构建的起点•RUN 命令用于在容器内运行任何命令(当然前提是命令必须存在)•COPY 命令用于从 Dockerfile 所在的目录拷贝文件到容器指定的路径

是时候来构建我们的镜像了,运行以下命令:

如果你已经在 client 目录中

#(注意最后面有个点,代表当前目录)

docker build -t dream-client .

如果你回到了项目根目录

docker build -t dream-client client

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你已经在 client 目录中

#(注意最后面有个点,代表当前目录)

docker build -t dream-client .

如果你回到了项目根目录

docker build -t dream-client client

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-jN4MQvUp-1715802238742)]

[外链图片转存中…(img-xQFyhHCb-1715802238742)]

[外链图片转存中…(img-Oe9FopIN-1715802238742)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值