Software

guchengf.me stride towards Docker

是的,guchengf.me 所有线上服务已经大步迈向 Docker !

在即将进行下一次个人服务器搬迁的时候,我终于决定将现在服务器上所有的东西迁移到 Docker 内。其实在此之前已经经历过几次搬迁,为了避免后续无尽的搬迁体力工作,决定正式更换到 Docker 平台。

Docker 解决了我的哪些问题

1. 服务器与本地开发的环境一致性

其实我在开发过程中,很难做到本地开发和服务器的环境完全一致。由于我主业是 Web 前端工程师,众所周知,前端其实是对服务器环境需求最小的,不管是从部署还是维护,复杂度都非常低,少量的环境不一致也不会导致很严重的问题,Web 前端最关注的毫无疑问是客户端(即我们的用户环境,姑且将服务器到客户端的链路优化工作交给更专业的人)。

因为上面这些原因,我对于本地和服务器环境的一致性工作没有太丰富的经验,并且也做得不够好。在经历过一些 Docker 的线上使用之后,我发现 Docker 能非常有效地提高这种一致性,当我需要实现一致性的时候,就采用相同的 Docker 配置,不再需要我手动维护两个环境,我只需要准备好一份能满足我需求的 Docker 配置,就达成了本地和服务器同步的需求。

2. 重复性部署工作

我的个人服务器上运行了一些个人网站和服务,每一个新项目都需要我按照需求进行部署(包括我的个人项目和其他开源项目)。这里面有不少重复性的工作,但我不是专业运维,很难做好自动化工作。

Docker 则给了我一个很好的选择。

开源项目都已经广泛开始提供官方 Docker 镜像,我只需要使用他们提供的镜像,修改相应的配置文件(通常只有很少的配置项),就可以轻松将服务运行起来。

而我自己的项目,则可以整理出共同的需求,集中到同一个 Docker 容器进行管理,有官方镜像的优先使用官方镜像,其余部分再各自独立成容器。这种情况下,我就避开了部署项目时的重复工作。

3. 代码和数据的集中管理

采用 Docker 之后,服务器上的代码和数据得以按照我最喜欢的结构进行管理,毕竟它们的存在形式不再影响它们在服务器上的运行。

我是如何进行迁移工作

前期准备

毫无疑问,首先我整理出了需要迁移的所有内容。我的个人项目以 Web 前端和 NodeJS 为主,还有少量的 python 以及 Go 项目。其中大部分需要 nginx 作为 Web Server。

那么我需要准备好 nginx 容器(包括相应的配置文件),Node、python、Go的相应环境,还有数据库(mysql 和 mongodb)。

而那些我使用的开源项目,则只需要确认它们是否有相应的 Docker 镜像,对于需要 nginx 的,将它们和我的 nginx 容器关联起来(数据库我不想关联起来,并没有这个必要)。

在做了这些工作之后,我的 Docker 配置结构就已经清晰了。

  • nginx (全局统一,包括 https 需要的 letsencrpt),nginx 和各个 Web 服务关联的全局 network(我将它命名为 me_guchengf_net)
  • 单纯 Web 前端的 static(其实它们就只是静态文件的而已)
  • Node、python、Go 的项目(需要使用相应的语言运行时容器)
  • mysql 和 mongodb(集中到一起也方便我对数据库进行备份)

Docker 配置结构设计

然后我就开始 Docker 配置结构的设计。

按照上面整理的列表进行一级目录设计。

- nginx(基于 nginx-alpine 二次构建,主要加入了 brotli 的支持并调整了一些 nginx 的配置)。
- mysql
- mongodb
- websites(静态的 Web 前端项目)
    - web-1
    - web-2
- typecho(没错我的博客是 typecho 的程序,我做了一些小修改)
- node-proj-a
- python-proj-a
- go-proj-a
- nodejs(我对 Node 环境有一些自己的需求,直接在官方 Node 镜像上进行了二次构建)
- php(typecho 需要 pdo 的 php 扩展,官方镜像没有,二次构建加入进去)
- open-source-proj-a(使用的其他开源服务,基本就是一个 docker-compose.yml 配一个 config 文件)
- logs(日志文件也酌情集中在这里,方便我查找)
- init-scripts(全局初始化脚本)

基于上面这个结构,我编写了首批 docker-compose 配置文件和二次构建镜像的 Dockerfile 文件。这样我有特定需求的镜像就准备好了,各个服务的基础信息、互相的关联关系也明确了。

依赖关系

在整个结构中,只有 nginx 是对其他 Web 项目有反向依赖的,因为它需要知道每个项目的 nginx 配置,于是我就自行作出约定,每个项目都需要按照标准在项目下提供自己的 nginx 配置文件,在 nginx 容器启动前去每个项目中获取对应的配置,复制并挂载到 nginx 容器内(有自己服务进程的做反向代理,单纯的 Web 前端项目则按照标准的静态项目配置)。

其余项目则都是单向依赖,需要互相访问的就建立对应的 network 进行关联,就可以直接通过容器进行访问,不再需要将端口绑定到宿主机上,也免去了很多麻烦。

实现每一个 Docker compose 并测试

这一步我就正式开始编写各个 docker-compose.yml,每个项目都自行实现项目到 Docker 目录的部署方法,最终的代码和数据在 Docker 容器中挂载。我的大部分项目结构都很简单,最终的成品代码挂载到容器中就可以了,也不存在文件系统的特殊需求。然后配置好需要关联容器生命周期的脚本(大多都是程序启动的前置)。

将项目中对应的线上环境配置替换成 Docker 的版本,比如数据库地址之类的。

只要每一个 docker compose 都可以顺利启动就可以了,在这个过程中我几乎没有遇到问题,项目都顺利运行。

上线

到这里,我就已经完成了所有迁移工作,剩下的就是搬上服务器了。

在服务器上准备好 Docker 环境(已经 docker-compose),将这个 Docker 项目直接发布到服务器上,首先运行初始化脚本,构建好所有定制镜像,然后将数据库服务启动,再依次启动各个项目,最后运行 nginx(因为nginx会检查upstream)。

是的,就和上述这些文字描述的一样,非常轻松,没有太多的曲折。

你现在阅读到的这篇文章就是已经运行在 Docker 容器中的了!

Amazing

我已经完成了整个迁移工作,并且将我本地开发的 nginx、数据库之类的服务也替换成了 Docker 版本,只需要区分开发和线上的配置文件,就实现了这些环境的一致性。

其实在这整个过程中,我遇到的最大困难竟然是 nginx 中 https 的部分,也就是 letsencrypt 和 certbot 的相关工作,我准备在下一篇文章中详细讲述这个问题。

Enjoy Docker!