部署 关于用 Docker 离线部署我的做法和讨论

palytoxin · 2021年09月03日 · 最后由 Rei 回复于 2021年09月06日 · 513 次阅读

背景

一个 rails 的项目没有互联网环境,离线部署,只能用光盘刻录数据。以往没有这种部署经验,基本是摸着石头过河。

系统镜像 Centos 7.4 Ruby 2.7.3 Rails 6.1.4

以往的项目都在 AWS 或者阿里云上,基本都是实体机环境直接安装依赖 cap 部署。

这次因为项目依赖较多集成了一些调用 py 的 OCR,和人脸识别功能,数据库还需要对接第三方的 Oracle 数据。因为乱七八糟的环境较多,所以当时考虑用 docker 安装部署。

方案

  1. 打个 Docker 进去整体运行。 网上参考的资料较多较杂,流程大概和论坛里差不多(https://ruby-china.org/topics/32459 类似)。这个方案测试了一下就直接放弃了,因为依赖多,打出来的包非常大,因为只能用光盘复制数据,更新代码必须整个环境打包,排除此方案。
  2. 将运行环境打包进 docker。代码和 gems 单独打包,更新代码时候只更新源代码,运行时将源代码目录挂载到 docker 中运行。

实施

Dockerfile

FROM ruby:2.7-slim
WORKDIR /
RUN sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list && \
  sed -i 's/security.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list && \
 apt-get update && \
 apt-get install -y wget
RUN wget http://test.example.com/chi_sfz.traineddata && \
  wget http://test.example.com/chi_sim.traineddata && \
  wget http://test.example.com/chi_sim_vert.traineddata
RUN apt-get install -y g++ autoconf automake libtool pkg-config libpng-dev libjpeg62-turbo-dev libtiff5-dev zlib1g-dev libleptonica-dev ca-certificates git libicu-dev libpango1.0-dev libcairo-dev make && \
  git clone --depth 1 https://gitee.com/mirrors/Tesseract-OCR.git /tesseract && \
  cd /tesseract && \
  ./autogen.sh && \
  ./configure && \
  make

FROM ruby:2.7-slim
ENV APP_HOME=/app \
    DEBIAN_FRONTEND=noninteractive \
    RAILS_ENV=production \
    EDITOR=vim \
    LD_LIBRARY_PATH=/opt/oracle/instantclient \
    NODE_OPTIONS="--max-old-space-size=8192" \
    BUILD_PACKAGES="build-essential" \
    DEV_PACKAGES="apt-utils git curl gnupg ca-certificates tzdata openssl ruby-dev imagemagick nodejs cron vim libpq-dev libxml2-dev libxslt-dev libaio1 cmake python3  python3-dev python3-pip python3-wheel python3-setuptools tesseract-ocr libtesseract-dev" \
    REMOVE_PACKAGES="cmake"

COPY --from=0 /tesseract /tesseract
RUN mkdir $APP_HOME && \
      echo 'gem: --no-document' >> ~/.gemrc && \
      echo -e "registry=https://registry.npm.taobao.org\nsass_binary_site=https://npm.taobao.org/mirrors/node-sass/\nphantomjs_cdnurl=http://npm.taobao.org/mirrors/phantomjs\nELECTRON_MIRROR=http://npm.taobao.org/mirrors/electron/" >> ~/.npmrc && \
      bundle config set mirror.https://rubygems.org https://gems.ruby-china.com && \
      gem sources --add https://gems.ruby-china.com/ --remove https://rubygems.org/ && \
      bundle config set path 'vendor/bundle' && \
      bundle config set without 'development test' && \
      gem install bundler && \
      sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list && \
      sed -i 's/security.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list && \
      apt-get update && \
      apt-get install -y $BUILD_PACKAGES $DEV_PACKAGES --no-install-recommends && \
      pip3 config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple && \
      pip3 install dlib && \
      pip3 install face_recognition && \
      apt-get remove -y $REMOVE_PACKAGES && \
      echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
      curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
      apt-get update && \
      apt-get install yarn && \
      rm -rf /var/lib/apt/lists/* && \
      apt-get clean autoclean && \
      apt-get autoremove --yes && \
      rm -rf /var/lib/{apt,dpkg,cache,log}/ && \
      yarn config set registry https://registry.npm.taobao.org/
RUN cd /tesseract && make install && /sbin/ldconfig && \
      rm -rf /tesseract

COPY --from=0 /*.traineddata /usr/local/share/tessdata/
COPY instantclient /opt/oracle/instantclient
COPY start.sh /

WORKDIR $APP_HOME
RUN chmod +x /start.sh
# CMD ["/start.sh"]

这个方案是把所有运行以及编译环境安装好,这样在本地打包的时候可以复用这个 docker 去 bundle 安装其他依赖。

makefile

CURRENT_DIR = $(shell pwd)
RUBY_DOCKER_TAG = myrails:2.7-slim
NGINX_DOCKER_TAG = openresty/openresty:alpine
REDIS_DOCKER_TAG = redis:alpine
POSTGRES_DOCKER_TAG = postgres:alpine
SRC_PACKAGE = src.tar.gz
DOCKER_PACKAGE = docker.tar.gz

.PHONY: make_docker_deps package release clean docker build_src clean_tmp

build_src:
    docker run --rm -v ${CURRENT_DIR}:/app -i -t ${RUBY_DOCKER_TAG} make release
clean:
    docker run --rm -v ${CURRENT_DIR}:/app -i -t ${RUBY_DOCKER_TAG} make clean_tmp

release: make_docker_deps package

make_docker_deps:
    bundle install
    bundle exec rake assets:clobber
    yarn install
    bundle exec rake assets:precompile
    bundle exec rake webpacker:compile

package:
    @echo 'release...'
    tar \
        --exclude='./tmp' \
        --exclude='./.git' \
        --exclude='./log/' \
        --exclude='./node_modules' \
        --exclude='./yarn-error.log' \
        --exclude='./docker_conf/instantclient' \
        --exclude='*.docker' \
        --exclude='${SRC_PACKAGE}' \
        --exclude='*.tar.gz' \
        -czf ${SRC_PACKAGE} .
    @echo "release tar end."

clean_tmp:
    /bin/rm -rf ./node_modules/*
    /bin/rm -rf ./vendor/*
    /bin/rm -f ${SRC_PACKAGE} *.docker ${DOCKER_PACKAGE}

docker\:base:
    docker build ./docker_conf -t ${RUBY_DOCKER_TAG} -f docker_conf/Dockerfile

docker:
    docker save -o ruby.docker ${RUBY_DOCKER_TAG}
    docker save -o nginx.docker ${NGINX_DOCKER_TAG}
    docker save -o redis.docker ${REDIS_DOCKER_TAG}
    docker save -o postgres.docker ${POSTGRES_DOCKER_TAG}
    tar czvf ${DOCKER_PACKAGE} ruby.docker nginx.docker redis.docker postgres.docker

这里用 make 去打包生成部署的文件,运行环境

docker-compose.yml

version: '3.5'

services:
  openresty:
    image: openresty/openresty:alpine
    restart: always
    ports:
      - 80:80
      - 443:443
    environment:
      - TZ=Asia/Shanghai
    volumes:
      - $PWD/docker_conf/nginx.conf:/usr/local/openresty/nginx/conf/nginx.conf:ro
      - $PWD/docker_conf/dhparam.pem:/usr/local/openresty/nginx/conf/dhparam.pem:ro
      - $PWD/docker_conf/nginx-selfsigned.crt:/usr/local/openresty/nginx/conf/nginx-selfsigned.crt:ro
      - $PWD/docker_conf/nginx-selfsigned.key:/usr/local/openresty/nginx/conf/nginx-selfsigned.key:ro
    depends_on:
      - ruby
    networks:
      - local_network
  ruby:
    image: myrails:2.7-slim
    restart: always
    environment:
      - TZ=Asia/Shanghai
      - RAILS_ENV=production
      - PIDFILE=/tmp/server.pid
      - RAILS_SERVE_STATIC_FILES=true
      - RAILS_LOG_TO_STDOUT=true
      - DATABASE_HOST=postgres
      - DATABASE_USERNAME=username
      - DATABASE_PASSWORD=password
      - REDIS_URL=redis://redis:6379/0/cache
    volumes:
      - "$PWD:/app"
      - "$PWD/docker_conf/start.sh:/start.sh"
      - "/project/storage:/app/storage"
    depends_on:
      - postgres
      - redis
    networks:
      - local_network
    command: ["bash", "/start.sh"]
  postgres:
    image: postgres:alpine
    restart: always
    shm_size: '1gb'
    ports:
      - 5432:5432
    volumes:
      - "/project/dbdata:/var/lib/postgresql/data"
      - "$PWD/docker_conf/postgres.conf:/etc/postgresql/postgresql.conf"
    environment:
      - TZ=Asia/Shanghai
      - POSTGRES_USER=username
      - POSTGRES_PASSWORD=password
    networks:
      - local_network
  redis:
    image: redis:alpine
    restart: always
    environment:
      - TZ=Asia/Shanghai
    volumes:
      - "$PWD/docker_conf/redis.conf:/etc/redis/redis.conf"
      - "/project/redisdb:/data"
    networks:
      - local_network
networks:
  local_network:
    external: true

这里是用 docker-compose 配合 systemd 写的服务去控制项目的运行 项目存储的文件都放在/project 目录

疑问

目前项目的部署和启动没有什么大的问题。但是相比实体机安装,cap 部署,这种方式无疑速度最慢,在部署过程中重启 docker-compose 冲断服务上时间更长。 另外这个项目也会对接一些 ftp 的终端,需要检测别的设备 ftp 上传的文件。目前计划在 docker-compose 中新增一个 ftp 容器,然后把 ftp 目录分别挂在到 rails 和 ftp 的容器中。

目前有几个问题希望和大家讨论一下:

  1. 请问各位有无好一些的方式来改善,新代码部署后重启 docker-compose 带来的服务中断问题?
  2. 最近测试了一下 dokcer swarm 的集群模式,但是对数据库持久化,还有 ftp 存放的问题都没有很好的解决。比如只在 swarm 相关的讨论中只能找到用 nfs 去挂实体机目录,但是用户那边 nfs 一些端口安全问题也不能开
  3. 当然可能也是我不太会用,请问部署这种离线的应用,正确的做法是什么?如果想引入容器集群的话,有没有最佳实践?
  1. 可以尝试在 openrestry 中代理 ruby ruby-backend container。 交替启动,最后 ruby 启动完成后删除 ruby-backend.
  2. 不了解 swarm
  3. 这个服务器够的话还是可以的。
awking 回复

请问 1 中,openresty 是在 docker-compose 中运行的,如果重启势必会一并把 openresty 干掉。而单独重启 rails 的服务并且交替启动,我该如何在 docker-compose 中操作? 原理可以理解成再启动一个 container 然后 web server 替换掉再干掉旧的,但是具体操作不太会

头一次听到还有用光盘部署 Docker 的 :0

palytoxin 回复
  1. 假设 一般 openrestry 很少会重启,那么重启的时候交替重启 rubybackup1, ruby

openrestry 负载到两个 container 上

upstream app_production {
  server ruby
  server ruby-backup
}

docker-compose 中使用多个 container

openresty
  depends_on
     - ruby
     - ruby-backup1

启动的时候启动备用的

docker-compose up -d ruby-backup1 
sleep 20

docker-compose stop ruby
docker-compose start ruby
sleep 20

docker-compose stop ruby-backup1
awking 回复

明白了 我去试试 谢谢🍺

===

update:

stop 再 start 其实只是停止了 container 的运行,并没有移除,所以是没有更新的。 需要 docker-compose rm container 一下

Hobr 回复

就是用 docker 搞了套运行环境而已

需要 登录 后方可回复, 如果你还没有账号请 注册新账号