努力工做,而后进入梦乡,“工做”和“作梦”之间好像没有任何关联;编写代码,而后部署应用,这二者彷佛也是天各一边。然而果然如此吗?这篇文章将经过《盗梦空间》的方式打开 Docker,让你实现从“作梦”到“筑梦”的实质性转变。在原先的“作梦”阶段(手动配置和部署),一切都充满了随机性和不可控性,你有时甚至都没法回忆起具体作的每一步;而在“筑梦”阶段(借助 Docker),你将经过自动化、高度可重复且可追踪的方式轻松实现任何配置和部署任务。但愿读完这篇文章的你,也能成为一个优秀的“筑梦师”!html
若是您以为咱们写得还不错,记得 点赞 + 关注 + 评论 三连,鼓励咱们写出更好的教程💪
不少朋友跟咱们反馈说,“一杯茶”纯粹就是忽悠人,写那么长,怎么可能在一杯茶的时间内看完?实际上,“饮茶”的方式因人而异,不一样的读者自有不一样的节奏。你彻底能够选择一目十行、甚至只浏览一下插图,几分钟的时间便能看完;也能够选择跟着咱们一步一步动手实践,甚至在有些地方停下来思考一番,虽然须要花更多的时间,可是咱们相信这份投入的时间必定是值得的。前端
其次,咱们想确认你是不是这篇文章的受众:node
最后,每一个小节的结构都是实战演练 + 回忆与升华。回忆与升华部分是笔者花了很多时间对优质资源进行搜集和整合而成,并结合了自身使用容器的经验,相信可以进一步加深你的理解,若是你赶时间的话,也能够略过哦。nginx
PS:这篇文章并无像常规的 Docker 教程同样上来就郑重其事地讲 Docker 的背景、概念、优点(颇有可能你已经听到耳朵生茧了hhh),而是彻底经过实践的方式直观地理解 Docker。在最后,咱们仍是会贴出经典的 Docker 架构图,结合以前的操做体验,相信你会有了然于胸的感受。git
在正式阅读这篇文章以前,咱们但愿你已经具有如下条件:程序员
如今假定你手头已经有了一个 React 编写的“梦想清单”项目,以下面这个动图所示:github
咱们将在这篇文章中教你一步步用 Docker 将这个应用容器化,用 Nginx 服务器提供构建好的静态页面。docker
固然咯,这篇文章做为一篇入门性质的教程,如下进阶内容不会涉及:数据库
以上进阶知识咱们会立刻推出相关教程,敬请期待。npm
咱们推荐各个平台用如下方式安装 Docker(通过咱们反复测试哦)。
菜鸟教程中详细介绍了 Win7/8 以及 Win10 的不一样推荐安装方法。注意 Win10 建议开启 Hyper-V 虚拟化技术。
可经过点击官方下载连接下载并安装 DMG 文件(若是速度慢的话能够把连接复制进迅雷哦)。安装完毕以后,点击 Docker 应用图标便可打开。
对于各大 Linux 发行版(Ubuntu、CentOS 等等),咱们推荐用官方脚本进行安装,方便快捷:
curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh
而后推荐将 docker
的权限移交给非 root 用户,这样使用 docker
就不须要每次都 sudo
了:
sudo usermod -aG docker $USER
注销用户或者重启以后就会生效。而后经过 systemd
服务配置 Docker 开机启动:
sudo systemctl enable docker
默认的镜像仓库 Docker Hub 在国外,国内拉取速度比较感人。建议参考这篇文章配置镜像加速。
镜像(Image)和容器(Container)是 Docker 中最为基础也是最为关键的两个概念,前者就是筑梦师的图纸,根据这张图纸的内容,就可以生成彻底可预测的梦境(也就是后者)。
提示若是你以为这个比喻难以理解,那么能够经过面向对象编程中“类”(class)和“实例”(instance)这两个概念进行类比,“类”就至关于“镜像”,“实例”就至关于“容器”。
在略微接触了镜像与容器这两个基础概念以后,咱们打算暂停理论的讲解,而先来一波小实验让你快速感觉一下。
按照历史惯例,咱们运行一下来自 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 为咱们默默作了如下事情:
hello-world:latest
镜像(latest
是镜像标签,后面会细讲),若是没有,执行第 2 步,不然直接执行第 3 步hello-world:latest
镜像建立一个新的容器并运行其中的程序感受太简单?咱们来尝试一个高级一点的:运行一个 Nginx 服务器。运行如下命令
docker run -p 8080:80 nginx
运行以后,你会发现一直卡住,也没有任何输出,但放心你的电脑并无死机。让咱们打开浏览器访问 localhost:8080
:
这时候熟悉 Nginx 的朋友可能就坐不住了:就一个简简单单的 docker run
命令,就搞定了 Nginx 服务器的安装和部署??没错,你能够继续访问一些不存在的路由,好比 localhost:8080/what
,一样会提示 404。这时候咱们再看 Docker 容器的输出,就有内容(服务器日志)了:
总结一下刚才 Docker 作的事情:
nginx:latest
镜像(关于 latest
标签,后面会细讲),若是没有,执行第 2 步,不然直接执行第 3 步nginx:latest
镜像建立一个新的容器,并经过 -p
(--publish
)参数创建本机的 8080 端口与容器的 80 端口之间的映射,而后运行其中的程序提示端口映射规则的格式为
<本机端口>:<容器端口>
。Nginx 容器默认开放了 80 端口,咱们经过设置8080:80
的端口映射规则,就能够在本机(容器以外)经过访问localhost:8080
访问,甚至能够在同一局域网内经过内网 IP 访问,这篇文章的最后会演示哦。
看上去很酷,不过像 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 服务器容器的一些信息:
0bddac16b8d8
(你机器上的可能不同)nginx
nginx -g 'daemon of...
,这个是 Nginx 镜像自带的运行命令,暂时不用关心0.0.0.0:8080->80/tcp
,意思是访问本机的 0.0.0.0:8080
的全部请求会被转发到该容器的 TCP 80 端口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....):
这张图乍一看颇具视觉冲击力,甚至会让你感受不知所措。没事,咱们大体地解读这张图里面的四类元素:
docker
开头的文字):包括 docker run
、docker create
、docker stop
等等create
、start
、die
、stop
还有 OOM
(内存耗尽)等等OK,这张图仍是很难一会儿理解,不过还记得刚才咱们作的四个小实验吗?咱们实际上走了一共两条路径(也是平常使用中走的最多的路),接下来将一一进行分析。
如上图所示:
docker run
命令,直接建立(create)并启动(start)一个容器,进入到运行状态(Running)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-world
、nginx
和 ubuntu
),这些镜像均可以在 Docker Hub 镜像仓库中找到。在这一步,咱们将开始筑梦之旅:学习如何容器化(Containerization)你的应用。
正如开头所说,咱们将容器化一个全栈的”梦想清单“应用,运行如下命令来获取代码,而后进入项目:
git clone -b start-point https://github.com/tuture-dev/docker-dream.git cd docker-dream
在这一步中,咱们将容器化这个用 React 编写的前端应用,用 Nginx 来提供前端页面的访问。
容器化包括三个阶段:
构建 Docker 镜像主要包括两种方式:
docker commit
命令根据修改后的容器建立新的镜像docker build
命令直接建立镜像因为篇幅有限,这篇文章只会讲解使用最为普遍的第二种建立镜像的方式。
咱们先把前端项目 client
构建成一个静态页面。确保你的机器上已经安装 Node 和 npm(点击这里下载,或使用 nvm
),而后进入到 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!建立 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
能够看到咱们指定了 -t
(--tag
,容器标签)为 dream-client
,最后指定了构建容器的上下文目录(也就是 当前目录 .
或 client
)。
运行以上的命令以后,你会发现:
Sending build context to Docker daemon:66.6MB
并且这个数字还在不断变大,就像黑客科幻电影中的场景同样,最后应该停在了 290MB 左右。接着运行了一系列的 Step(4 个),而后提示镜像构建成功。
为啥这个构建上下文(Build Context)这么大?由于咱们把比“黑洞”还“重”的 node_modules 也加进去了!(忍不住想起了下面这张图)
Docker 提供了相似 .gitignore 的机制,让咱们能够在构建镜像时忽略特定的文件或目录。建立 client/.dockerignore
文件(注意 dockerignore 前面有一个点):
node_modules
很简单,咱们只想忽略掉可怕的 node_modules。再次运行构建命令:
docker build -t dream-client .
太好了!此次只有 1.386MB,并且速度也明显快了不少!
终于到了容器化的最后一步——建立并运行咱们的容器!经过如下命令运行刚才建立的 dream-client
镜像:
docker run -p 8080:80 --name client -d dream-client
与以前相似,咱们仍是设定端口映射规则为 8080:80
,容器名称为 client
,而且经过 -d
设置为后台运行。而后访问 localhost:8080
:
成功了!一开始定下的三个梦想也都完成了!
提示甚至,咱们已经能够经过内网来访问“梦想清单”了。Linux 或 macOS 的同窗能够在终端输入
ifconfig
命令查询本机内网 IP,Windows 的同窗则是在 CMD 输入ipconfig
查询本机内网 IP,通常是以10
、172.16
~172.31
或192.168
开头。例如个人内网 IP 是192.168.0.2
,那么在同一局域网下(通常是 WiFi),能够用其余设备(好比说你的手机)访问192.168.0.2:8080
。
在刚才的实战中,你也许已经注意到在拉取和构建镜像时,Docker 老是会为咱们加上一个 :latest
标签,这个 :latest
的含义即是“最新”的意思。和软件的版本机制同样,镜像也能够经过标签实现“版本化”。
注意
latest
字面上的意思的确是“最新的”,但也只是一个普通的标签,并不能确保真的是“最新的”,更不会自动更新。更多讨论请参考这篇文章。
实际上,咱们彻底能够在拉取或构建镜像时指定标签(一般被认为是一种好的作法):
docker pull nginx:1.13 docker build -t dream-client:1.0.0
还能够给现有的镜像打上标签:
# 把默认的 latest 镜像打上一个 newest 标签 docker tag dream-client dream-client:newest # 甚至能够同时修改镜像的名称和标签 docker tag dream-client:1.0.0 dream-client2:latest
能够看到,标签未必必定是版本,还能够是任何字符串(固然最好要有意义,不然过了一阵子你也不记得这个打了这个标签的容器有什么做用了)。
Dockerfile 其实是默认名称,咱们固然能够取一个别的名字,例如 myDockerfile
,而后在构建镜像时指定 -f
(--file
)参数便可:
docker build -f myDockerfile -t dream-client .
这里举两个经典的使用场景:
Dockerfile.dev
用于构建开发镜像,建立 Dockerfile.prod
构建生产环境下的镜像;Dockerfile.cpu
用于构建用 CPU 训练的镜像,建立 Dockerfile.gpu
构建用 GPU 训练的镜像。通过刚才的容器化实战,相信你对镜像和容器的关系又有了新的理解。请看下面这张图:
在以前的“小试牛刀”环节中(用绿色箭头标出),咱们:
docker pull
从 Docker 镜像仓库拉取镜像到本地docker run
命令,根据镜像建立并运行容器docker stop
等命令操做容器,使其发生各类状态转变而在这一节的容器化实战中(用红色箭头标出),咱们:
docker build
命令,根据一个 Dockerfile 文件构建镜像docker tag
命令,给镜像打上标签,获得一个新镜像docker commit
命令,将一个现有的容器转化为镜像是时候拿出经典的 Docker 架构图了:
能够看到,Docker 遵循经典的客户端-服务器架构(client-server),核心组成部分包括:
dockerd
命令docker
)至此,这篇 Docker 快速入门实战教程也就结束啦,但愿你已经对 Docker 的概念和使用有了初步的理解。后续咱们还会发表 Docker 进阶的内容(例如 Network 网络、Volume 数据卷、Docker Compose 等等),手把手带你们部署一个全栈应用(先后端和数据库)到云主机(或任何你可以登陆的机器),敬请期待~
想要学习更多精彩的实战技术教程?来 图雀社区逛逛吧。