给零基础同学讲清楚docker

5,330 阅读11分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

前言,如何给零基础的前端同学讲清楚docker属实是不小的挑战,看似简单的内容,却不知道从何入手,或者说,我自己学到的三分皮毛,怎么好意思给大家share?与后端同学经过几轮深入讨论后,决定从最基础的使用开始,告诉大家docker是什么,怎么用。

本文目的

如果你的时间和精力很充裕,最直接有效的方式是看官方文档跟着Get started一步一步操作,一天的时间足矣学会。

希望能够帮助您了解docker。

本文将介绍:

  • 如何安装docker(作为合理的闭环,该环节需要存在)
  • 初识docker,简单的Getting startted。
  • 跑一个简单的应用
  • 构建自己的镜像
  • 分享最佳实践
  • 配置项优化

安装

你需要把docker桌面端(大于500MB的安装包)安装到本地。

Mac Intel 点我

Mac Apple 点我

Windows 点我

当然,如果你需要更多类型的安装包,你需要去这里看看

概念

  • Docker 码头工人???集装箱???

想象一下,要把15头大象从西双版纳运到昆明分几步。如果有直升机的话,可以一次运送一头,或者使用15架直升机;或者把15头大象放一起,放在一个超级大的集装箱中,找一架超级大的飞机一次性运往昆明;当然大象更愿意自己走过去,实际它们也是那么做的。那么问题来了,如果再从昆明运送回西双版纳,是不是集装箱这种方式更方便?回去的时候,可以选择坐火车,同时还可以把大象爱吃的菠萝放在另一个集装箱,也不用担心被大象吃掉,这就是Docker。

- 规整摆放
- 标准化
- 不互相影响
  • Containers / Apps 容器 / 应用
  • Images 镜像
  • Volumes 挂载

初识

安装成功后,是这个界面

image.png

我们点击start或者Skip tutorial跳过教程,我们这里点击skip,直接往后看

image.png

命令行执行

docker run -d -p 80:80 docker/getting-started
  • -d 后台运行,启动成功后会打印容器id
  • -p 指定内外映射端口号
  • docker/getter-started 执行的镜像名称

命令会在本地下载并启动docker/getting-started镜像

~ $ docker run -d -p 80:80 docker/getting-started
Unable to find image 'docker/getting-started:latest' locally
latest: Pulling from docker/getting-started
540db60ca938: Pull complete 
0ae30075c5da: Pull complete 
9da81141e74e: Pull complete 
b2e41dd2ded0: Pull complete 
7f40e809fb2d: Pull complete 
758848c48411: Pull complete 
23ded5c3e3fe: Pull complete 
38a847d4d941: Pull complete 
Digest: sha256:10555bb0c50e13fc4dd965ddb5f00e948ffa53c13ff15dcdc85b7ab65e1f240b
Status: Downloaded newer image for docker/getting-started:latest
8bc8dcde4b87fb57620d0df43f0da3b14688a2c27f15709692ed59497f2127c4

切换到Images,可以看到刚刚pull的镜像

image.png

切换到Containers,可以看到已经在运行的容器,可以使用浏览器访问教程

image.png

那么,这整个过程中发生了什么?

  • 启动了一个nginx服务
  • 静态化一些html页面

我们通过Inspect选项或者在容器界面直接点击镜像名称,可以查看到镜像详细构建过程

image.png

image.png

可以通过CLI入口进入容器,查看下物理文件

image.png

再理解概念

  • Container,一个完全独立的本机进程,和其他所有进程完全隔离。
  • Image,类似于我们安装操作系统时所需要的那个iso光盘镜像,通过运行这个镜像来完成各种应用的部署
  • Layer,层,每个docker镜像就像一张千层饼。

简单应用

我们接下来,暂时抛开docker,先看个简单的基于expressdemo

git clone git@github.com:fuchunhui/docker-start.git
cd docker-start
npm install && node server.js

代码的结构如下:

image.png

  1. server.js是主入口
import express from 'express';
import path from 'path';
import word from './word.js';

const app = express();

app.use(express.json());
app.use(express.text());
app.use(express.raw());
app.use(express.urlencoded({ extended: false }));

const __dirname = path.resolve();
app.use(express.static(path.join(__dirname, 'public')));

app.get('/get', (req, res) => {
  res.send('get request success.');
});

app.get('/test', (req, res) => {
  res.send(word.random().join('</br>'));
});

app.post('/faces', (req, res) => {
  const num = req.body?.num || 10;
  res.send(word.random(num).join('\n\n'));
});

app.listen(8080);

app监听三个接口

  • /get 服务端返回get request success.提示
  • /test 随机返回颜文字
  • /faces 随机返回指定数量的颜文字,并换行显示

npm run dev后,浏览器访问get

image.png

npm run post测试post请求

image.png

2.word.js是基于百度输入法,构建的颜文字字库,提供一个random(num)函数,随机返回指定个数的颜文字

3.test.js本地测试word.js功能。

image.png

4.public/index.html提供一个静态页面,提供一个按钮获取数据,点击后,调用server.jstest请求,显示一个颜文字。

2021-09-27 10.52.25.gif

到这里,我们的示例演示介绍完成,那么,请思考一下,如果我们本机环境没有Node怎么办?除了在本机安装Node外,我们还可以借用Docker的能力,构建我们自己的镜像,把示例Demo跑起来。

继续下面内容之前,我们先按住control + c,停止本地node服务

构建镜像

我们需要一个叫做Dockerfile的文件,Docker会自动读取它的文本内容构建镜像,Dockerfile是一个命令行的集合。

让我们开始吧,先构建一个Node服务。

准备node环境

touch Dockerfile

然后在Docker Hub查找合适的Node镜像,docker hub和我们熟知的npmjs.com一样,是所有镜像的源,我们通过搜索查找Node官方镜像

image.png

接下来,编辑Dockerfile文件

FROM node:16
CMD ["node", "--version"]

构建node16版本的镜像,默认输出当前版本信息。然后执行build命令

docker build -t node:16 .

在.(当前目录下)执行docker build,并生成name为node,tag为16的镜像。

docker-start (main) $ docker build -t node:16 .
[+] Building 440.6s (5/5) FINISHED                                                                                    
 => [internal] load build definition from Dockerfile                                                             0.0s
 => => transferring dockerfile: 43B                                                                              0.0s
 => [internal] load .dockerignore                                                                                0.0s
 => => transferring context: 2B                                                                                  0.0s
 => [internal] load metadata for docker.io/library/node:16                                                      70.2s
 => [1/1] FROM docker.io/library/node:16@sha256:7a888d7030be38976daabcd0881ff6564fb05885046fef9d08eb6002fa651  370.1s
 => => resolve docker.io/library/node:16@sha256:7a888d7030be38976daabcd0881ff6564fb05885046fef9d08eb6002fa6516f  0.0s
 => => sha256:e1b0cde43c0e73ca4ef26ae54aed841b7e5152f6dd510ad5e5e30cb771265f02 7.60kB / 7.60kB                   0.0s
 => => sha256:8f04e8168e3873638397ca4beb7d8484b150eca0d10fe1b033a125202ba57692 50.44MB / 50.44MB                91.6s
 => => sha256:c1c8f1c77d6674046d7deb41be1ca07f25cb43fd67f87e879ee79cc6586087f0 10.00MB / 10.00MB                 6.0s
 => => sha256:7a888d7030be38976daabcd0881ff6564fb05885046fef9d08eb6002fa6516fb 1.21kB / 1.21kB                   0.0s
 => => sha256:2465d69d013cb7ea85444fac62310badce86e89c54716999eae31577ec2bc6f3 2.21kB / 2.21kB                   0.0s
 => => sha256:82e5f66f5d0e1c97622f33d44fb04efb42bd3562bdc3482537d121040c789f9a 7.83MB / 7.83MB                  17.9s
 => => sha256:5095cab277710f0c2883844158323ad986c763ffc37353ddff874dd85585d9b6 51.84MB / 51.84MB                25.8s
 => => sha256:ea7fe362a971515971cf53613a30cc824f94d544272a5e061eb6365923ccbc11 192.39MB / 192.39MB             357.4s
 => => sha256:9000ed6ad54103b2ede6fec607e8b3f7cb6a2610158eb76c6091eecd65961179 4.20kB / 4.20kB                  27.1s
 => => sha256:bda6f8304dc4da996112a502fe92f5636bd29849f06aa75527febd6910342f74 34.19MB / 34.19MB                43.4s
 => => sha256:74bed10e609e5fb224fd52136389aa57e9b8c35c675be45211e4cd29a8e5675c 2.26MB / 2.26MB                  45.8s
 => => sha256:5bacd780a65fbe617d4d47adf55e74f97d8d0143a021ea55557c75e5c3ae4978 282B / 282B                      46.3s
 => => extracting sha256:8f04e8168e3873638397ca4beb7d8484b150eca0d10fe1b033a125202ba57692                        2.8s
 => => extracting sha256:82e5f66f5d0e1c97622f33d44fb04efb42bd3562bdc3482537d121040c789f9a                        0.4s
 => => extracting sha256:c1c8f1c77d6674046d7deb41be1ca07f25cb43fd67f87e879ee79cc6586087f0                        0.4s
 => => extracting sha256:5095cab277710f0c2883844158323ad986c763ffc37353ddff874dd85585d9b6                        3.1s
 => => extracting sha256:ea7fe362a971515971cf53613a30cc824f94d544272a5e061eb6365923ccbc11                        9.0s
 => => extracting sha256:9000ed6ad54103b2ede6fec607e8b3f7cb6a2610158eb76c6091eecd65961179                        0.1s
 => => extracting sha256:bda6f8304dc4da996112a502fe92f5636bd29849f06aa75527febd6910342f74                        2.2s
 => => extracting sha256:74bed10e609e5fb224fd52136389aa57e9b8c35c675be45211e4cd29a8e5675c                        0.2s
 => => extracting sha256:5bacd780a65fbe617d4d47adf55e74f97d8d0143a021ea55557c75e5c3ae4978                        0.0s
 => exporting to image                                                                                           0.1s
 => => exporting layers                                                                                          0.0s
 => => writing image sha256:88211c2be135416f38e0814a33a644ca4224f13ce946b831f4cfcd44f52e2a82                     0.0s
 => => naming to docker.io/library/node:16 

我们打开桌面端,在Images目录,可以看到刚生成Node镜像

image.png

点击RUN

2021-09-27 12.04.55.gif

默认的镜像,会按照约定的方式,执行node --version命令,表示镜像构建成功,接下来我们把docker-start内容部署到docker上。

部署可执行程序

继续编辑Dockerfile文件

FROM node:16

WORKDIR /docker-start

COPY . .
RUN npm install --production
CMD [ "node", "server.js" ]

设定docker-start为工作目录,然后把.目录的所有内容,拷贝到docker-start目录下,npm install后启动server服务。

我们通过命令行的方式构建新的镜像,并运行镜像。

docker-start为镜像名,v1为tag,在.(当前目录下)执行build操作

docker-start (main) $ docker build -t docker-start:v1 .
[+] Building 8.2s (9/9) FINISHED                                                                  
 => [internal] load build definition from Dockerfile                                         0.0s
 => => transferring dockerfile: 152B                                                         0.0s
 => [internal] load .dockerignore                                                            0.0s
 => => transferring context: 2B                                                              0.0s
 => [internal] load metadata for docker.io/library/node:16                                   0.0s
 => [1/4] FROM docker.io/library/node:16                                                     0.0s
 => [internal] load build context                                                            0.1s
 => => transferring context: 25.34kB                                                         0.0s
 => CACHED [2/4] WORKDIR /docker-start                                                       0.0s
 => [3/4] COPY . .                                                                           0.1s
 => [4/4] RUN npm install --production                                                       7.8s
 => exporting to image                                                                       0.1s
 => => exporting layers                                                                      0.1s
 => => writing image sha256:c9822dcaf7fdda7107445d13acbc184f269e52909244fefa577c6f2f6299b70  0.0s
 => => naming to docker.io/library/docker-start:v1 

因为我们刚刚构建过node16的镜像,所以再次基于node16构建镜像,速度很快,利用了上一次镜像的缓存。

docker run -dp 8080:8080 docker-start:v1

通过run命令,启动服务。-d后台运行,'-p'指定内外映射端口号,因为我们在server.jsapp.listen(8080),为方便使用,直接1对1映射。

再次打开浏览器,访问http://localhost:8080/

试试吧,刚刚的东西又出来啦。

  • 浏览器访问http://localhost:8080/get,检测是否显示get request success.
  • 浏览器访问http://localhost:8080,点击获取数据按钮,检测是否出现颜文字
  • 命令行执行npm run post,检测是否输出指定数量的颜文字,当然也可以直接curl或者使用其他的post请求检测

构建优化

我们的命令行构建过程,COPY . .把本地所有的内容都拷贝到目标目录下,当然,这就必然包含了node_modules的内容,这不是我们想看到的,所以同.gitignore一样,我们创建.dockerignore文件,忽略node_modules,并重新执行构建。

touch .dockerignore
echo 'node_modules'>.dockerignore

为了检测是否生效,我们先注释掉这两行代码

FROM node:16

WORKDIR /docker-start

COPY . .
# RUN npm install --production
# CMD [ "node", "server.js" ]

重新执行构建,并使用tag:v2标记新的镜像

docker build -t docker-start:v2 .

image.png

启动docker-start:v2,看一下里面内容,是否包含node_modules内容

docker run -it --name=start2 docker-start:v2 /bin/bash

image.png

镜像发布

基本同npm的发布流程一样,先要注册账号,然后本地登录成功后,使用push命令发布。

docker-start:v3为例说明,发布到个人账号目录下。

先移除Dockerfile文件中的两行注释,然后修改server.js,增加listen后的回调

app.listen(8080, () => {
  console.log('server start.');
});

构建v3版本

docker build -t docker-start:v3 .
docker tag docker-start:v3 fuchunhui/docker-start

查看下镜像,并重命名为USER_NAME/image:tag,如果不写tag,默认使用latest

image.png

hub.docker.com创建名称为docker-start的repo

image.png

本地登录,并push镜像

docker login
docker push fuchunhui/docker-start

上传的速度,取决于镜像的大小和网络状况,我们小等一会儿,待完成后,我们刷新hub查看

image.png

Playground

我们还可以通过palyground来运行我们的镜像

image.png

登录成功后,我们pull下刚刚发布的镜像,并启动。

docker run -dp 8080:8080 fuchunhui/docker-start

执行完成后,然后点击右上角的OPEN PORT输入8080,然后回车确定。

image.png

这可是白嫖4个小时的Server啊,Close后,还能继续白嫖,这简直太爽了吧。

可以通过logs命令查看刚刚的callback是否执行

docker container ls // 获取container id
docker logs containerId

image.png

其他内容

鉴于篇幅原因,对其他内容不再过多介绍,如下内容也非常精彩,是使用docker的开始,推荐大家按照官方文档step by step的动手实操。

  • compose 顾名思义,组合式,使用yaml文件,可以同时执行多个任务,类似于github的Action或者ci.yml

  • volumes 挂载,通常用于共享DB,无论我们是v1,v2,还是v3都可以使用同一个数据库,并保证数据的可持久化。官网给的例子是todolist,每次新版本构建后,都可以看到上一次写好的todo。

  • network 网络,由于每个容器之间都是互相隔离的,如果两个容器想要通信,就需要使用network,先创建network,并在该network下面,运行两个容器,就可以互相通信了,比如实现A容器访问B容器的数据。

最佳实践

个人总结的几点最佳实践,供参考。

  • 每个镜像,尽可能的干净,只提供单一的服务。
  • 构建镜像,按独立镜像来推进,一个一个的build,能够保证服务的可追溯。(想想美味的千层饼)
  • 对于需要安装的特定资源,可以考虑在不影响其他功能的前提下,在最上层,加载进来,比如npm的canvas包(因为它特别难以安装),可以在构建镜像的时候,保证node_modules的可用,解决后续npm install的麻烦。
  • 单个镜像内,控制层的个数,层数越少,镜像的体积越小,应该想尽办法获取体积更小的镜像,从而保证更快的传输速度和部署效率。
  • 选择合适的基础镜像,例如针对不同环境的node镜像,两者在功能集上有本质的区别
镜像node:16是基于debian:buster构建

镜像node:16-stretch是基于debian:stretch构建
  • 个人使用docker,多使用docker-compose

建议配置项

  1. 我们打开桌面端的配置中心,你会发现,你岌岌可危的硬盘空间,被Docker占用了60G,这里我建议设置成20G,足够使用。注意,每次修改配置项,保存后,本地构建的所有镜像,容器,挂载等等全部会清除,相当于一次初始化设置啦,所以请慎重操作,做好备份。

image.png

  1. 我们打开Mac自带的活动监视器,搜一下docker后台偷偷跑的进程

image.png

是不是有点吓人,不管你是否运行桌面端,这些内容都会在后台运行着(当然,使用体验确实不错),所以在不使用docker的时候,要把它们通通kill,减少CPU的负载。

总结

开阔自己的眼界,让思路不那么狭隘和局限。作为是软件工程师,最终是解决问题,解决问题有很多种方式,我们应该选择最合适的那个,而不是我们最擅长的那一套。

QA

分享后,收集大家的问题,汇总在这里

参考资料