背景

我们已经基于标准shell建立了一整套服务器环境搭建体系,在平常运维中,其实只要记住那一步执行哪个shell脚本就好了。但是执行了一段时间后,还是觉得不够灵活。想象如下场景:有一天你刚回收完一套环境,这个时候来了个需求需要尽快搭建一套独立环境之类。这个时候需要重新执行一次所有的脚本,时间比较慢,并且数据可靠性也无法保证。这个时候我想到了docker,虽然我们有在测试环境部分使用它的功能,但并没有真正拿他做到开箱即用

以下的流程只是一个测试例子,真实环境需要对不同环境来写不同的镜像。由于我比较懒,所以暂且把所有服务都放在一个镜像里面。

基本流程

  1. 基于centos 镜像
  2. 准备环境的镜像
  3. 导出镜像/容器

基于centos镜像

这个比较简单,可以到docker hub上面找centos:7, 然后编写dockerfile

FROM centos:7 AS s1
ENV container docker
RUN (cd /lib/systemd/system/sysinit.target.wants/; for i in *; do [ $i == \
systemd-tmpfiles-setup.service ] || rm -f $i; done); \
rm -f /lib/systemd/system/multi-user.target.wants/*;\
rm -f /etc/systemd/system/*.wants/*;\
rm -f /lib/systemd/system/local-fs.target.wants/*; \
rm -f /lib/systemd/system/sockets.target.wants/*udev*; \
rm -f /lib/systemd/system/sockets.target.wants/*initctl*; \
rm -f /lib/systemd/system/basic.target.wants/*;\
rm -f /lib/systemd/system/anaconda.target.wants/*;
VOLUME [ "/data/game" ]
CMD ["/usr/sbin/init"]

这里主要设置的就是根据不同环境而挂载的VOLUME。目的是为了让镜像无状态化,中间的数据都需要存储到宿主机。

制作环境准备的镜像

上一步只是完成了一些最基本的设置,接下来需要按照应用或者游戏需要应用到的服务来安装和启动。

  1. 安装网络和设置yum源(通常已经有了)
  2. 更新yum
  3. 安装软件
  4. 配置端口

s2依赖于s1

FROM allinone:s1 AS s2
USER root
RUN yum -y install net-tools \
  && yum -y install wget \
  && yum remove epel-release \
  && wget https://mirrors.ustc.edu.cn/epel//7/x86_64/Packages/e/epel-release-7-11.noarch.rpm \
  && rpm -ivh epel-release-7-11.noarch.rpm \
  && yum clean all \
  && yum makecache \
  # update热更新
  && yum provides '*/applydeltarpm' \ 
  && yum install deltarpm -y \
  && yum -y update \
  # 安装sshpass
  && yum -y install sshpass \
  # 安装subversion
  && yum -y install subversion \
  # 安装xinetd
  && yum -y install xinetd \
  #&& yum -y install ntpd \
  && yum -y install rsync \
  && yum install -y unzip zip \
  && yum -y install crontabs

CMD ["/usr/sbin/init"]

s3又依赖于s2

FROM allinone:s2 AS s3
USER root
RUN yum -y install nginx \
  # 安装supervisor
  && yum -y install python-setuptools \
  && easy_install supervisor \
  # 安装 redis
  && yum -y install redis \
  # 安装 mysql
  && wget http://dev.mysql.com/get/mysql-community-release-el7-5.noarch.rpm \
  && rpm -ivh mysql-community-release-el7-5.noarch.rpm \
  && yum -y install mysql-community-server \
  && yum -y install mysql-community-client \
  # 安装 dotnet
  && rpm -Uvh https://packages.microsoft.com/config/rhel/7/packages-microsoft-prod.rpm \
  && yum update \
  && yum -y install aspnetcore-runtime-2.1 \
  && yum -y install dotnet-sdk-2.1

EXPOSE 80 3306 6379 7000 8001 8009 8010 8011 8012 8013
CMD ["/usr/sbin/init"]

这里要注意的是,我们不能在此步骤种进行任何的service或者systemctl之类后台任务的命令,否则会导致镜像建立的失败。

主要原因是:Docker的设计理念是在容器里面不运行后台服务,容器本身就是宿主机上的一个独立的主进程,也可以间接的理解为就是容器里运行服务的应用进程。一个容器的生命周期是围绕这个主进程存在的,所以正确的使用容器方法是将里面的服务运行在前台。

systemd维护系统服务程序,它需要特权去会访问Linux内核。而容器并不是一个完整的操作系统,只有一个文件系统,而且默认启动只是普通用户这样的权限访问Linux内核,也就是没有特权,所以自然就用不了!

当然我们可以稍后在运行容器时候使用特权模式就可以绕开这个问题了。

当我们运行上述dockerfile后,检查镜像列表

centos:7是最基础的,
然后基于它我们构建了allinone:s1, 由于它并没有实质性的安装任何软件,所以看到大小并没有变化,这里我们可以只是作一些最基础的设置
接下来看allinone:s2,可以看到大小有2G多,这里主要是安装应用需要的软件。在实际运用种可以根据服务器的角色不同而安装不同的软件。

这里,我们可以把上述所有的dockerfile合并成一个,利用target参数来分步执行。

导出镜像/容器

先找到容器 id导出成tar/zip都可以

docker export df5138d2e73d > test.tar

然后就可以把这个文件上传到公共服务器,其他服务器只需要去拉取这个镜像就可以了

cat test.tar | docker import - allinone:s3

正如前面描述,由于在镜像生成中无法进行systemctl或者service的后台工作,在实际运用中导出容器比较方便。我们可以在容器中将所有的服务都挂载后台起来后,然后再导出。这样才能做到真正的开箱即用。

不过开箱有一个要注意的地方,需要在run/create 容器命令最后加上对应的CMD(也就是启动命令,而不是简单的bash)
比如:

docker create -v /data/docker/kaki:/data/docker/kaki --privileged=true --name=kakigameserver -p 80:80 -i allinone:s3 /usr/sbin/init

如何制作和配置无关的容器

由于我们在编排docker时候,不大可能为了每套测试或者生产环境设置一个对应的容器。比较直接的做法是上线后替换配置文件的内容。这个操作用脚本还是可以做到的。但不停的替换文件内容,其实是一个比较容易出错的操作。

个人认为比较好的一种做法:启动文件固定成一个软连接的路径,或者可以称它为代理。到了不同环境利用脚本替换代理的指向即可。

引入docker-compose

可以引入docker-compose这种方案,来优化容器的管理和部属。这样就避免了繁琐的create和start。编排好yml文件后,直接up就可以启动整个容器了。一个例子:

version: '2'
services:
  all-in-one:
    image: "allinone:s3"
    build: ./
    environment:
      - JAVA_OPTS=-Dorg.apache.commons.jelly.tags.fmt.timeZone=Asia/Shanghai 
    ports:
      - 13306:3306
			- 18001:8001
    container_name: docker-test
    privileged: true
    restart: always
    volumes:
        - /data/game:/data/game
    ulimits:
      nproc: 65535
      nofile:
        soft: 20000
        hard: 40000

参考

dockerfile