9102 年了,学点 Docker 知识

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

Docker 能解决什么问题

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

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

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

演示环境

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

概念:镜像 vs 容器

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

安装 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:latest 的镜像,同样会先去镜像市场下载。启动完成打开 http://localhost:80 就能立马看到 Nginx 的欢迎页面。

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

docker exec -it 4591552a4185 bash

参数说明:

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

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。

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

(由于之前的 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。

再次打开 http://localhost:80 ,同样能看到 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 ,就能看到输出结果。

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

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!

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

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

总结

这篇文章演示了和前端相关的一些 Docker 操作,从中我们可以看到其对于软件的开发,测试,部署都带来了极大的便利。文中的内容仅仅是冰山一角,示例也只能作为学习使用,请不要直接用在生产环境。同时部分内容是个人理解,没有权威性,深入学习 Docker ,你应该去看详细的文档。

作者:HelKyle

链接: https://juejin.im/post/5c2c69cee51d450d9707236e

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章