研發環境容器化實施過程(docker + docker-compose + jenkins) 背景介紹 改造思路 容器構建 容器整合 自動構建容器 總結

背景介紹

目前公司內部系統(代號GMS)研發團隊,項目整體微服務規模大概是4+9+3的規模,4個內部業務微服務,9個是外部平臺或者基礎服務(文件資源/用戶中心/網關/加密等),3箇中間件服務(數據庫/Redis/Nacos)。
分爲2個組,迭代週期爲2周。需求和排期都是會有交叉,會保證每週都有迭代內容交付,另外技術部門也在進行性能優化以及代碼規約的重構。我們的Git管理模型使用的是AoneFlow,意味着同一時間可能會有多個研發特性分支進行中。出現的問題就是CI,我們集成使用的Jenkins,原本研發環境就只有一套Jenkins來構建,後來出現並行的特性分支,爲了支持開發聯調工作就重新搭建了一套環境,但是後面出現了更多的並行需求(例如對接口壓測的性能分支,底層基礎架構的升級分支,代碼規約調整的分支)。
現在的痛點是需要部署一個環境的成本太高,基本需要一個高級研發對於所有組件都瞭解,對於Linux系統瞭解。整套環境部署可能需要2天左右,而且過程特別複雜容易出錯。

改造思路

考慮是需要進行容器化改造,目前整個環境的管理還沒有基於容器化來實施,所以我們希望這次也是給團隊一個基本概念和練兵的機會。
因爲我們主要的訴求是環境部署,所以並沒有按照容器推薦的那樣,每個服務都單獨建立docker,而是爲了能夠快速的部署和構建將所有服務和中間件進行分塊。
目前分塊主要是分爲中間件服務,業務服務,依賴/底層服務這麼三大塊。這麼分的原因有下面一些:

  • 中間件包含數據庫、Nacos、Redis。這麼做的目的是因爲Nacos強依賴數據庫,數據庫也是所有微服務的基礎依賴之一。數據庫結構和Nacos的配置實際上每個迭代會有一些變化,所以將這些內容打包在一起,以版本區分會更簡單一些。
  • 依賴/底層服務包含非業務的服務(文件資源/用戶中心/網關/加密)。這些都是外部服務,迭代過程中的變化是比較少的,可以每隔幾個迭代打包一次。所以爲了操作便利所以統一打包成了一個鏡像。
  • 業務微服務,業務的微服務就是迭代開發過程中不斷修改和測試的內容,所以這塊是應該是要單獨的容器,並且還要和Jenkins關聯能夠更新。

這樣基本的容器劃分就確認了,整體使用docker-compose來進行容器管理,因爲實際的鏡像數量會稍微多一些,而且還有很多如端口等配置。

容器構建

思路確認之後就開始執行,我們將比較詳細的描述各個鏡像的構建過程。

基礎準備

服務器上首先需要安裝好 docker和docker-compose依賴。我們的docker的私服使用的Harbor。
接下來我們基本都是在準備所有的dockerfile,所以會建立一個基礎目錄,在/root/docker/下建立 gms 文件夾用於存放各個鏡像的dockerfile,以及docker-compose文件。
提供一個基礎鏡像用於其他鏡像生成,基礎鏡像需要包含java環境,以及一些環境基礎插件和工具,我們來看一下Dockerfile

FROM centos:7

RUN mkdir -p /home/project/vv/log

ADD jdk-8u211-linux-x64.tar.gz /home/project/
RUN mv /home/project/jdk1.8.0_211 /home/project/java
COPY entrypoint.sh /home/project/vv/
ENV JAVA_HOME /home/project/java
ENV CLASSPATH .:$JAVA_HOME/lib:$JAVA_HOME/jre/lib:$CLASSPATH
ENV PATH $JAVA_HOME/bin:$JAVA_HOME/jre/bin:$PATH
# running required command
WORKDIR /home/project/vv
RUN yum install -y wget curl net-tools openssh-server telnet nc && chmod +x entrypoint.sh

這裏可以關注的點在於,我們在這個基礎鏡像的文件夾內實際上是要把我們需要使用的文件都存儲好的,意思就是你需要複製進鏡像的文件都必須在你當前執行docker build命令的目錄中
然後就是這裏會需要存在設置環境變量。

接下來執行 docker build -t images:tag .
不要漏掉最後的那個. 那個實際上就是指定當前目錄。
完成後執行 docker push
這樣基礎鏡像就構建完成,我們的命名爲 172.16.6.248/gms-service/gms-base
172.16.6.248是我們內部的Harbor服務器。

中間件容器

中間件容器需要包含數據庫、Nacos、Redis。
上面也說過,Nacos依賴與數據庫,由於是研發環境Nacos使用的standalone模式。其中有一點需要注意,在Nacos中可能會設置數據庫、Redis等連接,無論原本使用的是ip還是域名,在這裏都需要改成是服務名稱,由於我們是使用類docker-compose並且採用network組網的形式將相關的服務都放在同一個網絡內進行多實例之間的隔離。Nacos指向的數據庫連接改爲本地。
我們來看一下目錄結構:

-rw-r--r--. 1 root root 336 12月 17 17:58 Dockerfile
-rw-r--r--. 1 root root 205 12月 17 18:34 gmsstore.sh
-rw-r--r--. 1 root root 532 12月 6 15:31 my.cnf
drwxr-xr-x. 12 root root 206 12月 6 15:24 mysql
drwxr-xr-x. 17 root root 8192 12月 26 14:19 mysqldata
drwxr-xr-x. 9 root root 125 12月 4 11:10 nacos
drwxrwxr-x. 6 root root 4096 12月 11 15:21 redis

Dockerfile不用解釋,由於包含多箇中間件,所以啓動命令打包成了shell。
mysql涉及到3個文件/文件夾,my.cnf是配置文件,mysql是程序本體,mysqldata是打包了所有相關庫數據。
nacos和redis文件夾也不用解釋了。

我們來看看這個Dockerfile:

FROM 172.16.6.248/gms-service/gms-base

RUN yum -y install libaio numactl

COPY mysql /home/project/mysql
COPY mysqldata /home/project/mysqldata
COPY my.cnf /etc/my.cnf
COPY nacos /home/project/nacos
COPY redis /home/project/redis
COPY gmsstore.sh /home/project
WORKDIR /home/project/
RUN chmod +x gmsstore.sh
ENTRYPOINT ./gmsstore.sh

這裏特別的地方在於mysql8需要安裝一些依賴纔可以運行,所以我們安裝了libaio numactl。

外部依賴容器

先來看下目錄結構

-rw-r--r--. 1 root root 358 12月 17 19:26 Dockerfile
-rw-r--r--. 1 root root 764 12月 16 17:29 gmsdependency.sh
-rw-r--r--. 1 root root 71509153 12月 16 17:30 vv-dict.jar
-rw-r--r--. 1 root root 63880862 12月 16 17:29 vv-encryption.jar
-rw-r--r--. 1 root root 51465237 12月 16 17:30 vv-gateway.jar
-rw-r--r--. 1 root root 69535661 12月 16 17:29 vv-message.jar
-rw-r--r--. 1 root root 171366034 12月 16 17:30 vv-resource.jar
-rw-r--r--. 1 root root 78130738 12月 16 17:29 vv-user.jar

這個套路和之前一樣,相關的服務已經打出了jar包放到打包目錄下,編寫shell腳本作爲所有應用啓動的統一入口。
接下來看下Dockerfile:

FROM 172.16.6.248/gms-service/gms-base

LABEL version="1.0"
LABEL description="vv-gms-den"
LABEL maintainer="[email protected]"
COPY jar/* /home/project/vv/
ENV JVM=""
ENV NACOS="127.0.0.1:9002"
RUN chmod +x /home/project/vv/gmsdependency.sh
EXPOSE 7003
EXPOSE 8100
EXPOSE 7002
EXPOSE 7004
EXPOSE 7001
WORKDIR /home/project/vv/
ENTRYPOINT ./gmsdependency.sh

在這裏我們環境變量中設置Nacos的地址,實際上Nacos的地址會使用服務名的方式進行訪問,在使用 java -jar 命令時直接設置到參數中,類似這樣:

java -jar vv-dict.jar --spring.cloud.nacos.config.server-addr=$NACOS --spring.cloud.nacos.discovery.server-addr=$NACOS

業務應用容器

業務應用容器反而是最沒啥好說的,只有一個單jar文件,然後一個啓動腳本。
這裏可能唯一需要注意一下的就是第一次啓動的問題,由於業務應用依賴於中間件,當啓動時mysql和Nacos可能還沒有那麼快啓動起來,所以可能會引發業務應用連接不上中間件自動退出,需要寫腳本檢測。
給出Dockerfile

FROM 172.16.6.248/gms-service/gms-base

LABEL version="1.0"
LABEL description="vv-gms-core"
LABEL maintainer="[email protected]"
COPY jar/* /home/project/vv/
ENV JVM=""
ENV NACOS="127.0.0.1:9002"
RUN chmod +x /home/project/vv/*.sh
EXPOSE 8102
WORKDIR /home/project/vv/

沒啥多解釋的了。

容器整合

所有的docker鏡像都已經構建完畢並且已經傳輸到了鏡像服務器上。接下來就是如何整合容器了。
之前已經說過本次的選型是docker-compose,沒有上k8s是因爲還沒有和運維同學協調好,我們使用docker-compose先做可行性測試。
docker-compose 的安裝很多教程,我列一下基本命令

yum -y install epel-release
yum -y install python-pip
yum -y install python-devel
pip --version
pip install --upgrade pip
pip install docker-compose 
docker-compose --version

接下來看一下 docker-compose.yml

version: '3'
services:
  gms-dependency:
    image: 172.16.6.248/gms-service/gms-dependency
    ports:
      - 13004:7001
      - 13005:7003
      - 13006:7004
      - 13007:17002
      - 13008:8100
    networks:
      - gmsnetwork
    environment:
      JVM:
      NACOS: gms-store:9002
    depends_on:
      - gms-gateway
    volumes:
      - "/home/project/vv/log/:/home/project/vv/log/"
    entrypoint: ./entrypoint.sh -d gms-store:3306,gms-store:9002 -c './gmsdependency.sh'
  gms-gateway:
    image: 172.16.6.248/gms-service/gms-gateway
    ports:
      - 13010:9001
    networks:
      - gmsnetwork
    depends_on:
      - gms-store
    volumes:
      - "/home/project/vv/log/:/home/project/vv/log/"
    entrypoint: ./entrypoint.sh -d gms-store:3306,gms-store:9002 -c 'java -jar vv-gateway.jar --spring.cloud.nacos.config.server-addr=gms-store:9002 --spring.cloud.nacos.discovery.server-addr=gms-store:9002 >/dev/null 2>&1'
  gms-oacore:
    image: 172.16.6.248/gms-service/gms-oacore:1.2.7
    ports:
      - 13009:8102
    networks:
      - gmsnetwork
    environment:
      JVM:
      NACOS: gms-store:9002
    depends_on:
      - gms-gateway
    volumes:
      - "/home/project/vv/log/:/home/project/vv/log/"
    entrypoint: ./entrypoint.sh -d gms-store:3306,gms-store:9002 -c 'java -jar vv-oa-core.jar --spring.cloud.nacos.config.server-addr=gms-store:9002 --spring.cloud.nacos.discovery.server-addr=gms-store:9002 >/dev/null 2>&1'
  gms-store:
    image: 172.16.6.248/gms-service/gms-store:1.2.6
    ports:
      - 13001:3306
      - 13002:9002
      - 13003:6379
    networks:
      - gmsnetwork
    volumes:
      - "/home/project/vv/log/:/home/project/vv/log/"
  gms-oaweb:
    image: 172.16.6.248/gms-service/gms-oaweb:1.2.6
    ports:
      - 13018:80
    networks:
      - gmsnetwork
    depends_on:
      - gms-oacore
      - gms-dependency
      - gms-gateway
    volumes:
      - "/home/project/vv/log/:/home/project/vv/log/"
    entrypoint: /home/project/vv/entrypoint.sh -d gms-oacore:8102,gms-dependency:17002,gms-gateway:9001,gms-xxladmin:8103 -c 'nginx -g "daemon off;"'
networks:
  gmsnetwork:
    driver: bridge

這份文件中,其實是比較常規的docker-compose的格式,由於各個容器之間相互可能都有依賴,所以我們使用了內部網絡,networks 這個特性,將相關應用放在同一個內部網絡中互相訪問。depends_on這個屬性支持了啓動的先後順序,但是這個屬性僅僅基於容器級別。也就是前置的容器只要啓動後續就會啓動,但是內部依賴的應用可能還沒有啓動完成,所以我們使用了shell腳本來檢測應用啓動完成後再實際的啓動應用。最後就是我們使用volumes開放了可掛載目錄,輸出所有的日誌文件便於查看。environment設置環境變量,將依賴的服務名和內部網絡端口傳遞給不同容器中的應用。
這樣就完成了docker-compose的設計,然後我們使用 docker-compose up -d 就可以啓動 docker-compose stop 可以關閉。但是切記docker-compose命令必須在存在docker-compose.yml文件的目錄下執行

自動構建容器

我們使用docker-compose已經啓動了完整的環境,但是記得本次實踐的目的在於研發環境的部署,研發環境是需要不斷的更新代碼進行調試的。所以我們需要引入jenkins來進行容器重新構建、推送、環境更新。

Maven相關

我們的項目是一個父子的Maven項目,父目錄下會包含業務核心代碼(core)、對外暴露API(api)等包。需要打包的鏡像實際上是core中的完整jar包。
Maven的插件我們使用的是

<build>
    <plugins>
        <plugin>
                <groupId>com.spotify</groupId>
                <artifactId>dockerfile-maven-plugin</artifactId>
                <version>1.4.10</version>
                <configuration>
                    <repository>172.16.6.248/gms-service/gms-oacore</repository>
                    <force>true</force>
                    <forceCreation>true</forceCreation>
                    <tag>${dev.docker.tag}</tag>
                    <buildArgs>
                        <JAR_FILE>vv-oa-core/target/${project.build.finalName}.jar</JAR_FILE>
                    </buildArgs>
                </configuration>
            </plugin>
    </plugins>
</build>

這是github上的插件地址,這裏force這個標籤是可以對同tag的鏡像在構建時進行覆蓋。
Dockerfile建議放在子項目根目錄下。
在Jenkins構建時,我們是以父項目作爲根目錄執行 package 命令的。打包完成後,不能直接執行 dockerfile:build 命令。而是在Post Steps中定製腳本,cd進入到子項目之後,分別執行 dockerfile:build dockerfile:push,我們來看一下Jenkins中配置的腳本:

cd gms-oacore

nowtag=1.3.1
nowpath=/root/docker/dockerbase/feature-VV-443
nowprefix=feature-VV-443

mvn -Dmaven.test.skip=true dockerfile:build -Ddev.docker.tag=$nowtag
mvn -Dmaven.test.skip=true dockerfile:push -DpushImageTag

ssh [email protected] "/root/docker/dockerbase/jenkins-rebuild.sh $nowpath "$nowprefix"_gms-oacore_1 172.16.6.248/gms-service/gms-oacore:$nowtag gms-oacore"

這裏實際上是在Jenkins先打包,並且build和push鏡像,ssh到目標服務器通過遠程腳本來進行拉取構建的一些操作。
針對docker-compose啓動的容器,如果是要單獨更新一個鏡像,可以將容器stop之後rm掉,同時rmi對應鏡像,最終使用 docker-compose --scale images:tag=1 重新拉取啓動這個鏡像。

非Maven項目

由於是前後端分離項目,所以我們還會有一個單獨的前端項目,是直接掛載Nginx容器內。所以這部分沒辦法使用Maven插件,我們就採用shell直接調用docker命令的形式,這裏放上差異的部分:

docker build -t 172.16.6.248/gms-service/gms-oaweb:$nowtag .
docker push 172.16.6.248/gms-service/gms-oaweb:$nowtag

替換來mvn dockerfile相關的命令,其他基本相同。

總結

在這樣的實踐中,我們將項目拆分爲合理粒度建立docker鏡像,使用docker-compose將多個容器打包爲一個完整環境運行,同時用內部網絡的概念隔離多個環境在同一個宿主機器時的影響,最後使用Jenkins來進行自動化的構建和發佈,完成了研發環境的完整閉環。效果也大大提高,原本需要花幾天時間還不能很完整的部署好,現在只需要一個人15-30分鐘就可以完整部署好一個環境。
但是實際上問題也很多,由於整合來大量的環境,所以單個環境啓動後,佔用內存10G左右,實際上比較難單個宿主機器直接部署多套。
另外大家也能發現,我們存在部分訪問是通過ip來的,這是一個不好的習慣,建議儘量都改爲內部域名的形式,避免後續服務器變更造成複雜影響。
在實施過程中,我們還是手寫了很多shell腳本作爲中間粘合,這個對於環境的依賴會比較大,而且複用性其實是很低的,後續我們會考慮如何提高可複用性。
最後還是要考慮實施k8s,這個應該在2020的Q1就會實施。

容器化是爲了能夠讓研發和運維對於應用的把握程度更高,避免大家花太多的時間在環境、部署之類問題上,也能夠大大提高系統的穩定性和擴展性。但是會對DevOps提出更高的要求,研發和運維要更加緊密的配合,架構設計、部署方案等都需要共同討論理解之後才能實施,但是我堅信這就是趨勢,我們越早迎合越早能提升自己、整個團隊和我們的產品。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章