行至水穷处

坐看云起时

博客Docker化实践

在写这篇博客的时候,系统已经稳定运行在docker容器上。本着折腾的精神,觉的还是应该将博客进行docker化,一来毕竟在内存上多花了不少钱,不充分利用一下有点说不不过去,最重要的是如果docker化,以后万一需要迁移博客到别的主机上,我只需要将相关容器打包一下拷贝过去起来就行了,简单快速。

这也是我第一次实践docker化工作,从小心谨慎以及可回退角度,做了以下目标设定:

  1. 将现有的所有服务docker化,包含 nginx,php-fpm,memcached,mysql

  2. docker化工作不能影响现有底层系统上的已有lnmp服务的配置,因为现有配置中有不少配置是会定期动态变化的,所以不能因为docker化改变现有的结构、配置等。同时要做到万一不用docker直接本地启动原来服务即可。 (这个需求其实埋下很多坑)

  3. docker化后ssllabs评分不能降低,必须还是A+

为了实现上述目标,直接pull一个现成的官方 nginx,官方php,甚至官方wordpress是不可行的,需要创建一个自己的容器来实现ct-transparency,brotli, dynamic-tls-record-sizing,以及openssl需要打上chacha20的patch。而为了简化工作,决定将php和nginx放到一个容器里。

Nginx-php容器化

在PHP官方的5.6.31-fpm-alphine镜像基础上补充编译nginx,并为php增加memached扩展支持。以下dockerfile在docker hub和阿里云docker镜像站上以及本地均build通过。在实际dockerfile制作过程遇到了不少问题,总结下来主要有:

  1. 为了兼容原来nginx配置环境的,需要仔细考虑编译过程中相关配置目录的规划以及相关目录的提前创建,防止在mount目录给容器后,导致原来的配置提示找不到相关引用文件,在这一点上经历了多次修改和重编译,踩了不少坑
  2. centos 7.3上如果docker的storage driver是 overlay方式,且linux本身是xfs格式系统+ftype=1,则可能会出现build时候无法rm -rf命令无法删除目录情况,这是由于centos的bug,参考https://github.com/docker/docker/issues/31717
    我是将docker启动的storage driver改为devicemapper方式来解决
  3. 其它因素,参考了不少别人的dockerfile,certificate transparency编译用的包的下载地址引用的是github上一个人的,但这个人突然有一天删除了那个文件,上一次build还通过了,下次build就因为无法下载失败了。。还好我有提前本地备份,索性将那几个需要的文件都改为在自己的github上提供
  4. 国内build有可能还会遇到dns无法解析问题,其实明明是正常的,比如在阿里云国内节点编译则会失败,选择阿里云境外节点则顺利完成
  5. 由于是nginx+php在同一个容器里需要同时暴露服务,这与容器本来的设计思想有些违背,所以使用了supervisord来实现前台启动双服务,需要注意的是supervisord里一样不能对nginx以daemon方式启动,否则supervisor会不断的提示nginx启动失败并重启

 

 

镜像制作的一点心得:尽可能的简化需求,力争实现容器本身是一个完整的个体,如果为了所谓的灵活导致过多的考虑外部数据的关联,会使得容器与外部环境耦合性太强,失去了容器本身的意义。但同时又要综合衡量容器化后,内外文件交互的必要性,找到一个灵活的平衡点。起初我是抱着一个容器打天下的思路去看别人的dockerfile,寄希望随便拿一个可以满足自己环境要求的dockerfile来编译,但看了大量dockerfile后发现每个人共享出来的dockerfile其实都是最适合他自己环境的,多多少都会有一些为自己环境定制的东西,所以docker镜像除非标准的官方通用镜像,否则真的是千人千面。正如我build的这个,如果你愿意,可以直接从github上取下并build,build完毕后可以启动使用,但是相关配置、乃至为了适应配置所用的临时证书等都不是通用的。

build后的镜像启动后,别看到启动成功了就认为成功了,一定要 docker logs去仔细看下

 

启动镜像

镜像在docker hub以及阿里云上通过拉取github来直接build,由于主机在阿里云上,所以拉取的时候直接从阿里云的docker镜像站拉取就行了。每个人都可以使用阿里云的docker镜像加速,如果自己本地测试也不防将register改为阿里云的,具体方法https://cr.console.aliyun.com登录后查看,每个人都可以得到一个专有的加速连接。

拉取到本地后,重新docker tag一下,不然阿里的仓库名称太长了。随后执行
docker run -itd -v /***/nginx/:/***/nginx/ -v /etc****:/etc/*** -v /***/wwwroot/:/***/wwwroot/:rw --name nginx -p 80:80 -p 443:443 myf5nginx

上述命令里隐去了一些路径。

容器成功启动:

启动后,首先会发现提示无法连接数据库,因为相关数据库连接配置是使用的localhost且相关权限是只容许本地访问,所以需要对mysql连接即相关数据库用户权限做下改造。

随后可以访问,但是访问网站很慢。这是由于博客启用了memcached,且是通过127.0.0.1来连接的,容器本身内部是没有memecached服务的,所以导致慢,暂时先停掉memcache 功能。

此时博客可以正常访问,而且感觉速度和以前一样。进入博客后台,提示有插件更新,于是更新插件,但是系统提示无权限,看来文件权限属性有了问题,进入容器内看下文件是啥权限:

uid,gid都是1001. 这个1001其实是宿主机本地的某用户的ID,在mount给容器后,容器内运行nginx/php的那个用户并不是这个id而是100,所以php提示无权更新文件。此时需要将容器的那个用户的uid改为1001即可,方法可以是:
1. 在容器内使用usermod去修改那个用户的uid,但是由于是alpine的基础镜像,usermod命令是不存在的,所以不可行

2. build容器的时候,就提前修改好和宿主机一致,这同样需要在容器里增加相关命令安装,但是修改出来的镜像很可能在放到别的宿主机上后,因为别的宿主机上同名用户的uid又不一样了而导致问题

3. 我是比较暴力的将宿主机上的该用户uid改成容器里的100,然后给目录重新chown,这样mount上去的文件就是100了,从而容器用户可以有权限

4. 启动容器时候使用特权参数,这个方法我没测试

由于这方面实践经验较少,不知道在一个复杂的大型生产环境里是如何有效解决这个问题的。从上面可以看出,其实要想实现一个镜像跑遍所有服务器,还不是那么容易的。

解决上述问题后,博客运行完全正常了,ssllabs跑分依旧是A+

mysql容器化

由于我的mysql没使用innodb,因此我直接将原来本地mysql所用的data目录直接mount给容器使用,这样设置后容器里的mysql确实可以读取到数据库,但是在宿主机查看这个目录时候发现文件用户被设置成了systemd-bus-proxy:input的名称,显然容器用其用户id覆盖了文件权限,这样会影响本地myql未来启动使用这些数据库文件,所以为了保险起见,决定mount一个独立空文件夹给容器,然后做一次标准的mysql数据库文件导入导出工作。这样mysql容器里变拥有了最新的网站数据。然后nginx容器使用link方式连接容器,并设置WP相关数据库server配置为link的别名。

这里无需将mysql暴露给宿主机,因为宿主机用不到,需要注意如果使用-p参数暴露了端口给宿主机,docker自己会自动增加一个iptables规则,这个规则可能会导致内部容器无法连接宿主机IP:port。

docker run --name mysql -v /***/dockerdata:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=nami0518 -d mysql:tag

memcached容器化

使用官方的memcached镜像启动,nginx-php容器启动时候link过去。根据WP的memcached插件提示在WP配置文件里增加自定义memcached server的配置,使用link时候设置的别名即可,例如memcached:11211

docker run -itd --name mymemcached memcached -m 64

最终Nginx容器的run命令是:

docker run -itd -v /***/nginx/:/***/nginx/ -v /***/letsencrypt:/***/letsencrypt/ -v /***/www/:/***/www/:rw --name nginx --link=mymemcached:memcached --link=mysql:mysql -p 80:80 -p 443:443 mynginx

Docker Compose管理所有容器

除了上述手工运行命令逐个容器启动外,还可以利用docker-compose来实现统一启动和管理,有点类似kubernetes的deployment。保存以下文件为docker-compose.yml, 然后在同目录下执行 docker-compose up -d 启动所有服务

其它处理,修改letsencrypt相关脚本适应容器化后的nginx配置加载

<EOF>

实践过程中参考了:

https://hub.docker.com/r/panascais/nginx/

https://hub.docker.com/r/frostiede/nginx/~/dockerfile/

https://github.com/ngineered/nginx-php-fpm/blob/master/Dockerfile

https://xlange.com/post/dockerfile-baseon-official-php-image.html

如果对此感兴趣,此文相关dockerfile及文件放置在github上:https://github.com/myf5/nginx-php-fpm

hub上镜像文件: https://hub.docker.com/r/myf5/

 

扫描二维码关注本博公众号:

微信公众号

微信公众号

点赞

发表评论

电子邮件地址不会被公开。 必填项已用*标注