lulujianglab / blog

:bento:lulujiang blog
https://lulujianglab.com/
83 stars 4 forks source link

都9102了,该实践 Docker 部署啦 #48

Open lulujianglab opened 5 years ago

lulujianglab commented 5 years ago

最近用 Docker 完成了 eggjs 后端项目的部署,不得不感叹,Docker 真的是太好用了。不仅能够一键安装 mysql,省去了很多搭环境的事宜,而且可以直接把项目发布到 Docker 容器上进行测试,等项目需要正式上线时,就直接把做好的 Docker 镜像部署上去就好了,省去了很多项目部署上线的风险

Docker 是什么

Docker 是一个可以用来快速部署的轻量级虚拟技术,允许开发人员将自己的程序和运行环境一起打包,制作成一个 DockerImage (镜像),然后部署到服务器上,通过下载这个 Image 就可以将程序跑起来,省去了每次都安装各种依赖和环境的麻烦

Docker 镜像

操作系统分为内核和用户空间。对于 Linux 而言,内核启动后,会挂载 root 文件系统为其提供用户空间支持。而 Docker 镜像(Image),就相当于是一个 root 文件系统。除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)

Docker 容器

镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的 类 和 实例 一样,镜像是静态的定义,容器是镜像运行时的实体

仓库(Docker Registry)

镜像构建完成后,可以很容易的在当前宿主机上运行,但是,如果需要在其它服务器上使用这个镜像,我们就需要一个集中的存储、分发镜像的服务,Docker Registry 就是这样的服务

Docker 部署应用有什么优点

使用 Docker 容器部署应用快速方便,特别是应用较多时部署迁移等使用 Docker 会更方便。另外,在同一台服务器上不能同时运行多个 eggjs 应用,除非停止另外一个 eggjs 应用

更详细的介绍可以参考非官方 Docker 中文文档

Docker 的架构

为了后续更好的理解 Docker 命令的操作,我们先来大致理清下 Docker 的架构

image

上面这张图大致介绍了 Docker 的架构,中间是 host,也就是进行 Docker 操作的宿主机,宿主机上主要是运行 Docker Daemon 的核心程序,也就是负责做各种各样的操作,比如说下载 Docker 的镜像,比如说运行一个容器

那宿主机如何和 Docker Daemon 交互呢?实际上是通过在客户端用命令比如 buildrunput 交给 DaemonDaemon 来做实际的操作

右边的蓝的是互联网的 Sass 服务,叫做 registryDaemon 可以和 registry 交互,比如说 push 一个 Image,拖拉一个 Image,实际上是所有 Docker 用户共享 Docker 镜像的服务

image

简单来说,就是客户端和守护进程 Daemon 进行操作,把命令送给守护进程,守护进程来拖取镜像,运行容器,和远端的镜像仓库进行交互

项目部署实践

我这里已经用 eggjs 开发了一个后端项目,然后需要构建一个镜像,然后基于这个 Image 运行一个 container。从而快速实现部署

大体流程

具体操作如下

创建 Dockerfile

如果不知道 Dockerfile 文件怎么写,可以直接到 github 上查找 eggjs / docker,就可以看到完整的 Dockerfile 文件,直接拷贝粘贴到项目路径下即可

# 拉取要创建的新镜像的 base image(基础镜像),类似于面向对象里边的基础类
FROM node:8.11.3-alpine

# 设置时区
ENV TIME_ZONE=Asia/Shanghai

# 在容器内运行命令
RUN \
  mkdir -p /usr/src/app \
  && apk add --no-cache tzdata \
  && echo "${TIME_ZONE}" > /etc/timezone \ 
  && ln -sf /usr/share/zoneinfo/${TIME_ZONE} /etc/localtime 

# 创建 docker 工作目录
WORKDIR /usr/src/app

# 拷贝,把本机当前目录下的 package.json 拷贝到 Image 的 /usr/src/app/ 文件夹下
COPY package.json /usr/src/app/

# 使用 npm 安装 app 所需要的所有依赖
RUN npm i

RUN npm i --registry=https://registry.npm.taobao.org

# 拷贝本地的所有文件到路径中去
COPY . /usr/src/app

# 暴露端口。如果程序是一个服务器,会监听一个或多个端口,可以用 EXPOSE 来表示这个端口
EXPOSE 7001

# 给容器指定一个执行入口
CMD npm run start

注意事项

同时,我们还可以在 eggjs 的官网看到应用部署的文档,有两点需要防止踩坑的地方

image

image

以上修改都是在 package.json 文件中

构建 Image

Dockerfile 创建完成后,我们就可以在 Dockerfile 文件所在的目录下运行下面的 Docker 命令来构建一个 Image

docker build -t webshare-backend .

通过 -t 的参数,给它一个标签 share,然后给出一个 .. 就是路径名 就是把这个路径底下的所有文件都送给 Docker Engine 让它来产生 Image

image

运行完最后会出现 successfully,代表构建成功

接着我们就可以通过 Docker Images 来查看是否真的生成了这个文件

image

的确是生成了一个新的 Image, 打了一个 taglatest,有一个 ImageId 和大小 size

接着就可以运行这个 Image

运行镜像

docker run -d -p 7001:7001 webshare-backend

image

返回了一个容器的 id,这样就成功的用 Dockerfile 的方式来构建了一个自己的 Image

通过运行 docker ps 可以查看容器是否启动成功

我们还可以用 curl localhost:7001 测试一下,会输出接口查询内容

上传镜像

可以通过 docker push 命令,把自己创建的镜像上传到仓库中来共享

docker tag webshare-backend:latest 服务器ip/webshare-backend:1.0
docker login -u 账号 -p 密码 服务器IP地址

后面显示 Login Succeeded,就是登录成功了

这儿可能第一次会报 Service Unavailable,需要去根路径的 .docker 目录下的 daemon.json 里添加信任

docker push 服务器IP/目录名/webshare-backend:1.0

更新应用

其他常用命令

镜像分层

Dockerfile 中的每一行都产生一个新层,存在 Image里的层是只读的(RO)

Image 被运行成为一个容器的时候,就会会产生一个新层,叫容器层 container layer,是可读可写的(RW)

分层的好处:如果有很多的容器和 Image,比如 A Image 有 10 层, B Image 有 7 层,他们之间可能有 5 层是共享的,那么无形之中,存储压力就会小很多

Volume( 数据卷)

提供独立于容器之外的持久化存储

因为在容器中的改动是不会被保存的,Volume 提供了比较方便的、可以持久化存储的一个技巧,比如说运行一个数据库容器,数据库的真正数据应该是被持久化的,Volume 是可以实现的,并且还可以提供给容器之间的共享数据