9102 年了,学点 Docker 知识

22,106 阅读4分钟

最近工作需要,开发时需要用到 Docker。这篇文章从零开始演示几个 Demo,如果你之前没接触过 Docker,可以一步步跟着操作,加深对 docker 的理解。

多图,请尽量在💻上观看

本文由 DJI 前端开发工程师 HelKyle 原创并分享于掘金,如需转载请注明出处

Docker 能解决什么问题

无论你所处的公司大或小,多多少少都遇到开发环境和生产环境不一致的问题。有些开发者用 Windows,有些开发者用 Mac,而生产环境可能用的是 Linux,同时跑着多个应用,每个应用依赖的 node 版本版本不一致,不同服务可能还占用相同的端口。于是我们常常听到这样的疑问:“本地明明是好的啊,为什么到线上就不行了?”

(我自己的亲身经历:很早以前开发前端项目,我需要在本地搭建 LEMP 环境,照着教程捣鼓好几天,一行前端代码都没有写。后来还因为一些“莫名其妙”的问题,反反复复重装了好几次。)

而使用了 Docker 之后呢,我们能通过配置文件一条命令快速构建环境,并且可以做到和其他服务隔离,互不影响,通过例子来讲解。

演示环境

这篇文章执行的环境是 macOS 10.14.1, Docker version 18.09.0

概念:镜像 vs 容器

镜像是以一些列历史操作叠加而成的,对镜像的每一次操作都会产生新的只读层,比方说你往容器写入内容,提交,再把它移除,则会产生两个历史只读层,有点像 git commits,而容器可以理解问为镜像历史层 + 可写层的可运行系统,在可写层做任何操作都没问题,不提交的话,容器被删除后相应的改动也会丢失,有点像 git 暂存区

(个人理解,不一定对)

docker

安装 Docker

Mac 安装 docker

brew cask install docker

其他环境安装 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:2557e3c07ed1e38f26e389462d03ed943586f744621577a99efb77324b0fe535
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

这条命令会连接本地 Docker 服务,Docker 服务检测到本地没有 hello-world 镜像,于是去 Docker 镜像市场下载这个镜像,然后创建新的容器,运行特定的命令,输出 Hello from Docker!

这篇文章不讲解 docker 有哪些基础命令,直接从案例入手。

启动 nginx 服务

docker run -d -p 80:80 --restart=always  nginx:latest

参数说明: run 启动某个镜像 -d 让容器在后台运行 -p 指定端口映射,宿主机的80端口映射到容器的80端口 --restart 重启模式,设置 always,每次启动 docker 都会启动 nginx 容器。

nginx

由于我本地没有 nginx:latest 的镜像,同样会先去镜像市场下载。启动完成打开 http://localhost:80 就能立马看到 nginx 的欢迎页面。

hello nginx

如果想修改欢迎页面,可以进入到容器内修改页面。

docker exec -it 4591552a4185 bash

参数说明:
exec 对容器执行某些操作
-it 让容器可以接受标准输入并分配一个伪tty
4591552a4185 是刚刚启动的 nginx 容器唯一标记
bash 指定交互的程序为 bash

exec

nginx 默认文件路径是 /usr/share/nginx/html/index.html ,直接用 echo 写入内容即可。

echo '<h1>Hello Docker<h1/>' > /usr/share/nginx/html/index.html

ctrl + D 退出容器,重新访问 localhost:80 即可看到 Hello Docker。

hello docker

每次修改内容都需要手动进入容器,太过繁琐,并且👆提到了,对容器的直接修改不会持久保存,如果容器被删,数据也会跟着丢失。

(由于之前的 demo 已经占用了 80 端口,咱们先 kill 掉它。)

docker kill 4591552a4185

Docker 提供数据挂载的功能,即可以指定容器内的某些路径映射到宿主机器上,修改命令,添加 -v 参数,启动新的容器。

docker run -d -p 80:80  -v ~/docker-demo/nginx-htmls:/usr/share/nginx/html/ --restart=always  nginx:latest

启动成功之后,docker 会帮你生成目录 ~/docker-demo/nginx-htmls,现在里面什么都没有,添加个 index.html。

html

再次打开 http://localhost:80, 同样能看到 hello docker。

hello docker

接着我们来用 Node + Redis + Docker 做一个 PV 展示的 DEMO。

运行命令:

docker run -d -p 6379:6379 -v ~/docker-demo/redis:/data redis:latest

启动一个 redis 容器,并将数据持久化到 ~/docker-demo/redis 目录。(考虑性能,redis 并不会实时写入数据到磁盘)

用 koa 启动一个 node server,并连接 redis , 每次访问 / 都给计数器加一。

const Redis = require('ioredis');
const Koa = require('koa');
const Router = require('koa-router');

const router = new Router();
const app = new Koa();

const redis = new Redis(`redis://127.0.0.1:6379/0`);

router.get('/', async (ctx, next) => {
  await next();
  await redis.incr('pv');
  const current = await redis.get('pv');
  ctx.body = `current pv: ${current}`;
});
 
app
  .use(router.routes())
  .use(router.allowedMethods());

app.listen(3000);

访问 http://localhost:3000,就能看到输出结果。

pv

推荐使用 medis 可视化查看 redis 数据。

medis

OK,开发环境完成功能开发,交付运维上线。我们假设生成环境会启动四个 node 服务,一个 redis 服务,和一个 nginx 做负载。

这时候需要把我们的 node 服务也构建成镜像,新增 dockerfile 文件。

# 基于最新的 node 镜像
FROM node:latest
# 复制当前目录下所有文件到目标镜像 /app/ 目录下
COPY . /app/
# 修改工作目录
WORKDIR /app/
# yarn 一下,安装依赖
RUN ["yarn"]
# 启动 node server
ENTRYPOINT ["node", "index.js"]

更多 dockerfile 指令看这里

可以在本地构建一下,运行命令 docker build . --tag=pv,然后通过 docker images 就能看到刚刚构建的新镜像。

继续往下走,编排一组容器,docker 官方提供了 docker-compose 工具。在项目目录下新增 docker-compose.yml 文件。

# 使用 docker-compose 2.2 版本
version: "2.2"
# 定义 services
services:
  redis:
    image: redis:latest
    volumes:
      - "~/docker-demo/pv/data/:/data/"

  web:
    # 放大4倍,也就会有四个 node server
    scale: 4
    build: .
    # 新增环境变量
    environment:
      - REDIS_HOST=redis://redis:6379/0
    # 依赖关系
    depends_on:
      - redis

  nginx:
    image: nginx:latest
    depends_on:
      - web
      - redis
    ports:
      - 80:80
    volumes:
      - "./default.conf:/etc/nginx/conf.d/default.conf"

更多 docker-compsoe 指令看这里

service web 新增环境变量 REDIS_HOST=redis://redis:6379/0 是给 ioredis 链接用的,对应的要修改 js 文件。

const redis = new Redis(process.env.REDIS_HOST);

redis://redis:6379/0 第一个 redis 是协议,第二个 redis 是 service host。service 之间可以通过 host 互相通信。

复制 nginx 容器下的 default.conf 文件出来修改

upstream web {
  server pv_web_1:3000;
  server pv_web_2:3000;
  server pv_web_3:3000;
  server pv_web_4:3000;
}

server {
    #...

    location / {
      proxy_pass http://web;
    }

    #...
}

新增上游服务 web,这里的 pv 是我的项目文件名,web 是 docker-compose 文件中定义的 service name, 1 - 4 则是 scale 出来 docker 自动给定的序号。启动起来之后,nginx 访问 http://pv_web_1:3000 的请求就会到达第一个 web 容器。

万事具备,let's compose up!

compose-up

好了,现在访问 http://localhost:80

result

到目前为止,我们已经把应用部署完成,每次访问 pv 数量自动加一,并且经过 nginx 负载均衡,会随机打到不同的容器上面。🎉🎉🎉

总结

这篇文章演示了和前端相关的一些 docker 操作,从中我们可以看到其对于软件的开发,测试,部署都带来了极大的便利。文中的内容仅仅是冰山一角,更全面的学习可以看Docker — 从入门到实践这本书。

示例也只能作为学习使用,请不要直接用在生产环境。同时部分内容是个人理解,没有权威性,深入学习 Docker ,你应该去看详细的文档。

本文由 DJI 前端开发工程师 HelKyle 原创并分享于掘金,如需转载请注明出处

相关文章