<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          GitLab持續(xù)集成

          共 13030字,需瀏覽 27分鐘

           ·

          2021-03-14 12:39

          互聯(lián)網(wǎng)軟件的開發(fā)和發(fā)布,已經(jīng)形成了一套標(biāo)準(zhǔn)流程,最重要的組成部分就是持續(xù)集成(Continuous integration,簡(jiǎn)稱CI)。

          持續(xù)集成

          持續(xù)集成指的是,頻繁地(一天多次)將代碼集成到主干。它的好處主要有兩個(gè):

          • 快速發(fā)現(xiàn)錯(cuò)誤。每完成一點(diǎn)更新,就集成到主干,可以快速發(fā)現(xiàn)錯(cuò)誤,定位錯(cuò)誤也比較容易。

          • 防止分支大幅偏離主干。如果不是經(jīng)常集成,主干又在不斷更新,會(huì)導(dǎo)致以后集成的難度變大,甚至難以集成。

          Martin Fowler 說(shuō)過(guò),"持續(xù)集成并不能消除 Bug,而是讓它們非常容易發(fā)現(xiàn)和改正。"

          持續(xù)集成

          持續(xù)集成強(qiáng)調(diào)開發(fā)人員提交了新代碼之后,立刻進(jìn)行構(gòu)建、(單元)測(cè)試。根據(jù)測(cè)試結(jié)果,我們可以確定新代碼和原有代碼能否正確地集成在一起。

          與持續(xù)集成相關(guān)的,還有兩個(gè)概念,分別是持續(xù)交付和持續(xù)部署。

          持續(xù)交付

          持續(xù)交付(Continuous delivery)指的是,頻繁地將軟件的新版本,交付給質(zhì)量團(tuán)隊(duì)或者用戶,以供評(píng)審。如果評(píng)審?fù)ㄟ^(guò),代碼就進(jìn)入生產(chǎn)階段。

          持續(xù)交付可以看作持續(xù)集成的下一步。它強(qiáng)調(diào)的是,不管怎么更新,軟件是隨時(shí)隨地可以交付的。

          持續(xù)交付

          持續(xù)交付在持續(xù)集成的基礎(chǔ)上,將集成后的代碼部署到更貼近真實(shí)運(yùn)行環(huán)境的「類生產(chǎn)環(huán)境」(production-like environments)中。比如,我們完成單元測(cè)試后,可以把代碼部署到連接數(shù)據(jù)庫(kù)的 Staging 環(huán)境中更多的測(cè)試。如果代碼沒(méi)有問(wèn)題,可以繼續(xù)手動(dòng)部署到生產(chǎn)環(huán)境中。

          持續(xù)部署

          持續(xù)部署(continuous deployment)是持續(xù)交付的下一步,指的是代碼通過(guò)評(píng)審以后,自動(dòng)部署到生產(chǎn)環(huán)境。

          持續(xù)部署的目標(biāo)是,代碼在任何時(shí)刻都是可部署的,可以進(jìn)入生產(chǎn)階段。

          持續(xù)部署的前提是能自動(dòng)化完成測(cè)試、構(gòu)建、部署等步驟。

          持續(xù)部署

          持續(xù)集成的操作流程

          根據(jù)持續(xù)集成的設(shè)計(jì),代碼從提交到生產(chǎn),整個(gè)過(guò)程有以下幾步。

          提交

          流程的第一步,是開發(fā)者向代碼倉(cāng)庫(kù)提交代碼。所有后面的步驟都始于本地代碼的一次提交(commit)。

          測(cè)試(第一輪)

          代碼倉(cāng)庫(kù)對(duì) commit 操作配置了鉤子(hook),只要提交代碼或者合并進(jìn)主干,就會(huì)跑自動(dòng)化測(cè)試。

          測(cè)試的種類:

          • 單元測(cè)試:針對(duì)函數(shù)或模塊的測(cè)試

          • 集成測(cè)試:針對(duì)整體產(chǎn)品的某個(gè)功能的測(cè)試,又稱功能測(cè)試

          • 端對(duì)端測(cè)試:從用戶界面直達(dá)數(shù)據(jù)庫(kù)的全鏈路測(cè)試

          第一輪至少要跑單元測(cè)試。

          構(gòu)建

          通過(guò)第一輪測(cè)試,代碼就可以合并進(jìn)主干,就算可以交付了。

          交付后,就先進(jìn)行構(gòu)建(build),再進(jìn)入第二輪測(cè)試。所謂構(gòu)建,指的是將源碼轉(zhuǎn)換為可以運(yùn)行的實(shí)際代碼,比如安裝依賴,配置各種資源(樣式表、JS腳本、圖片)等等。

          常用的構(gòu)建工具如下:

          • Jenkins

          • Travis

          • Codeship

          • Strider

          Jenkins 和 Strider 是開源軟件,Travis 和 Codeship 對(duì)于開源項(xiàng)目可以免費(fèi)使用。它們都會(huì)將構(gòu)建和測(cè)試,在一次運(yùn)行中執(zhí)行完成。

          測(cè)試(第二輪)

          構(gòu)建完成,就要進(jìn)行第二輪測(cè)試。如果第一輪已經(jīng)涵蓋了所有測(cè)試內(nèi)容,第二輪可以省略,當(dāng)然,這時(shí)構(gòu)建步驟也要移到第一輪測(cè)試前面。

          第二輪是全面測(cè)試,單元測(cè)試和集成測(cè)試都會(huì)跑,有條件的話,也要做端對(duì)端測(cè)試。所有測(cè)試以自動(dòng)化為主,少數(shù)無(wú)法自動(dòng)化的測(cè)試用例,就要人工跑。

          需要強(qiáng)調(diào)的是,新版本的每一個(gè)更新點(diǎn)都必須測(cè)試到。如果測(cè)試的覆蓋率不高,進(jìn)入后面的部署階段后,很可能會(huì)出現(xiàn)嚴(yán)重的問(wèn)題。

          部署

          通過(guò)了第二輪測(cè)試,當(dāng)前代碼就是一個(gè)可以直接部署的版本(artifact)。將這個(gè)版本的所有文件打包( tar filename.tar * )存檔,發(fā)到生產(chǎn)服務(wù)器。

          生產(chǎn)服務(wù)器將打包文件,解包成本地的一個(gè)目錄,再將運(yùn)行路徑的符號(hào)鏈接(symlink)指向這個(gè)目錄,然后重新啟動(dòng)應(yīng)用。這方面的部署工具有Ansible,Chef,Puppet等。

          回滾

          一旦當(dāng)前版本發(fā)生問(wèn)題,就要回滾到上一個(gè)版本的構(gòu)建結(jié)果。最簡(jiǎn)單的做法就是修改一下符號(hào)鏈接,指向上一個(gè)版本的目錄。

          使用 GitLab 持續(xù)集成

          從 GitLab 8.0 開始,GitLab CI 就已經(jīng)集成在 GitLab 中,我們只要在項(xiàng)目中添加一個(gè).gitlab-ci.yml文件,然后添加一個(gè) Runner,即可進(jìn)行持續(xù)集成。而且隨著 GitLab 的升級(jí),GitLab CI 變得越來(lái)越強(qiáng)大。

          概念

          Pipeline

          一次 Pipeline 其實(shí)相當(dāng)于一次構(gòu)建任務(wù),里面可以包含多個(gè)流程,如安裝依賴、運(yùn)行測(cè)試、編譯、部署測(cè)試服務(wù)器、部署生產(chǎn)服務(wù)器等流程。

          任何提交或者 Merge Request 的合并都可以觸發(fā) Pipeline,如下圖所示:

          +------------------+           +----------------+
          | | trigger | |
          | Commit / MR +---------->+ Pipeline |
          | | | |
          +------------------+ +----------------+
          復(fù)制代碼
          Stages

          Stages 表示構(gòu)建階段,說(shuō)白了就是上面提到的流程。我們可以在一次 Pipeline 中定義多個(gè) Stages,這些 Stages 會(huì)有以下特點(diǎn):

          • 所有 Stages 會(huì)按照順序運(yùn)行,即當(dāng)一個(gè) Stage 完成后,下一個(gè) Stage 才會(huì)開始

          • 只有當(dāng)所有 Stages 完成后,該構(gòu)建任務(wù) (Pipeline) 才會(huì)成功

          • 如果任何一個(gè) Stage 失敗,那么后面的 Stages 不會(huì)執(zhí)行,該構(gòu)建任務(wù) (Pipeline) 失敗

          因此,Stages 和 Pipeline 的關(guān)系就是:

          +--------------------------------------------------------+
          | |
          | Pipeline |
          | |
          | +-----------+ +------------+ +------------+ |
          | | Stage 1 |---->| Stage 2 |----->| Stage 3 | |
          | +-----------+ +------------+ +------------+ |
          | |
          +--------------------------------------------------------+
          復(fù)制代碼
          Jobs

          Jobs 表示構(gòu)建工作,表示某個(gè) Stage 里面執(zhí)行的工作。我們可以在 Stages 里面定義多個(gè) Jobs,這些 Jobs 會(huì)有以下特點(diǎn):

          • 相同 Stage 中的 Jobs 會(huì)并行執(zhí)行

          • 相同 Stage 中的 Jobs 都執(zhí)行成功時(shí),該 Stage 才會(huì)成功

          • 如果任何一個(gè) Job 失敗,那么該 Stage 失敗,即該構(gòu)建任務(wù) (Pipeline) 失敗

          所以,Jobs 和 Stage 的關(guān)系圖就是:

          +------------------------------------------+
          | |
          | Stage 1 |
          | |
          | +---------+ +---------+ +---------+ |
          | | Job 1 | | Job 2 | | Job 3 | |
          | +---------+ +---------+ +---------+ |
          | |
          +------------------------------------------+
          復(fù)制代碼

          基于 Docker 安裝 GitLab Runner

          GitLab Runner 簡(jiǎn)介

          理解了上面的基本概念之后,有沒(méi)有覺(jué)得少了些什么東西 —— 由誰(shuí)來(lái)執(zhí)行這些構(gòu)建任務(wù)呢?

          答案就是 GitLab Runner 了!

          想問(wèn)為什么不是 GitLab CI 來(lái)運(yùn)行那些構(gòu)建任務(wù)?

          一般來(lái)說(shuō),構(gòu)建任務(wù)都會(huì)占用很多的系統(tǒng)資源 (譬如編譯代碼),而 GitLab CI 又是 GitLab 的一部分,如果由 GitLab CI 來(lái)運(yùn)行構(gòu)建任務(wù)的話,在執(zhí)行構(gòu)建任務(wù)的時(shí)候,GitLab 的性能會(huì)大幅下降。

          GitLab CI 最大的作用是管理各個(gè)項(xiàng)目的構(gòu)建狀態(tài),因此,運(yùn)行構(gòu)建任務(wù)這種浪費(fèi)資源的事情就交給 GitLab Runner 來(lái)做拉!

          因?yàn)?GitLab Runner 可以安裝到不同的機(jī)器上,所以在構(gòu)建任務(wù)運(yùn)行期間并不會(huì)影響到 GitLab 的性能

          基于 Docker 安裝 GitLab Runner

          準(zhǔn)備目錄結(jié)構(gòu)

          目錄結(jié)構(gòu)

          docker-compose.yml

          version: '2'
          services:
          gitlab:
          image: twang2218/gitlab-ce-zh:10.5
          restart: always
          hostname: '10.3.50.160'
          container_name: gitlab
          environment:
          TZ: 'Asia/Shanghai'
          GITLAB_OMNIBUS_CONFIG: |
          external_url 'http://10.3.50.160:8080'
          gitlab_rails['gitlab_shell_ssh_port'] = 2222
          unicorn['port'] = 8888
          nginx['listen_port'] = 8080
          ports:
          - '8080:8080'
          - '8443:443'
          - '2222:22'
          volumes:
          - /etc/localtime:/etc/localtime
          - ./conf:/etc/gitlab
          - ./data/logs:/var/log/gitlab
          - ./data/data:/var/opt/gitlab
          gitlab-runner:
          image: gitlab/gitlab-runner
          restart: always
          hostname: gitlab-runner
          container_name: gitlab-runner
          extra_hosts:
          - git.imlcs.top:10.3.50.160
          depends_on:
          - gitlab
          volumes:
          - /etc/localtime:/etc/localtime
          - ./runner:/etc/gitlab-runner
          - /var/run/docker.sock:/var/run/docker.sock
          復(fù)制代碼

          GitLab CI 地址與令牌參數(shù)

          項(xiàng)目–>設(shè)置–>CI/CD–>Runner 設(shè)置

          查找位置

          注冊(cè) Runner

          方法一

          docker exec -it gitlab-runner  gitlab-runner register -n \
          --url http://10.3.50.160:8080/ \
          --registration-token cpR4sgBCsZ-TJUpJVz9t \
          --description "dockersock" \
          --docker-privileged=true \
          --docker-pull-policy="if-not-present" \
          --docker-image "docker:latest" \
          --docker-volumes /var/run/docker.sock:/var/run/docker.sock \
          --docker-volumes /root/m2:/root/.m2 \
          --executor docker
          復(fù)制代碼

          方法二

          docker exec -it gitlab-runner gitlab-runner register
          # 輸入 GitLab 地址
          Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/):
          http://10.3.50.160:8888/
          # 輸入 GitLab Token
          Please enter the gitlab-ci token for this runner:
          1Lxq_f1NRfCfeNbE5WRh
          # 輸入 Runner 的說(shuō)明
          Please enter the gitlab-ci description for this runner:
          可以為空
          # 設(shè)置 Tag,可以用于指定在構(gòu)建規(guī)定的 tag 時(shí)觸發(fā) ci
          Please enter the gitlab-ci tags for this runner (comma separated):
          deploy
          # 這里選擇 true ,可以用于代碼上傳后直接執(zhí)行
          Whether to run untagged builds [true/false]:
          true
          # 這里選擇 false,可以直接回車,默認(rèn)為 false
          Whether to lock Runner to current project [true/false]:
          false
          # 選擇 runner 執(zhí)行器,這里我們選擇的是 shell
          Please enter the executor: virtualbox, docker+machine, parallels, shell, ssh, docker-ssh+machine, kubernetes, docker, docker-ssh:
          #shell
          docker # 使用 docker 作為輸出模式
          Please enter the default Docker image (e.g. ruby:2.1):
          alpine:latest # 使用的基礎(chǔ)鏡像
          復(fù)制代碼

          使用 Runner

          .gitlab-ci.yml

          在項(xiàng)目工程下編寫.gitlab-ci.yml配置文件:

          示例一,找一個(gè)springboot的簡(jiǎn)單項(xiàng)目

          image: docker-maven:alpine
          services:
          - redis:3-alpine
          #Maven 阿里云鏡像
          #before_script:
          # - echo -e "<?xml version=\""1.0\"" encoding=\""UTF-8\""?><settings xmlns=\""http://maven.apache.org/SETTINGS/1.0.0\"" xmlns:xsi=\""http://www.w3.org/2001/XMLSchema-instance\"" xsi:schemaLocation=\""http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd\""><mirrors><mirror><id>alimaven</id><name>aliyun maven</name><url>http://maven.aliyun.com/nexus/content/groups/public/</url><mirrorOf>central</mirrorOf></mirror></mirrors></settings>" > ~/.m2/settings.xml
          # 定義 stages
          stages:
          # - test
          - build
          # 定義 jobs
          #test app:
          # stage: test
          # script:
          # - echo "I am test job"
          # - mvn test
          # 定義 job
          build app:
          stage: build
          script:
          - mvn -Dmaven.test.skip=true clean package docker:build
          復(fù)制代碼

          示例二

          stages:
          - install_deps
          - test
          - build
          - deploy_test
          - deploy_production
          cache:
          key: ${CI_BUILD_REF_NAME}
          paths:
          - node_modules/
          - dist/
          # 安裝依賴
          install_deps:
          stage: install_deps
          only:
          - develop
          - master
          script:
          - npm install
          # 運(yùn)行測(cè)試用例
          test:
          stage: test
          only:
          - develop
          - master
          script:
          - npm run test
          # 編譯
          build:
          stage: build
          only:
          - develop
          - master
          script:
          - npm run clean
          - npm run build:client
          - npm run build:server
          # 部署測(cè)試服務(wù)器
          deploy_test:
          stage: deploy_test
          only:
          - develop
          script:
          - pm2 delete app || true
          - pm2 start app.js --name app
          # 部署生產(chǎn)服務(wù)器
          deploy_production:
          stage: deploy_production
          only:
          - master
          script:
          - bash scripts/deploy/deploy.sh
          復(fù)制代碼

          上面的配置把一次 Pipeline 分成五個(gè)階段:

          • 安裝依賴(install_deps)

          • 運(yùn)行測(cè)試(test)

          • 編譯(build)

          • 部署測(cè)試服務(wù)器(deploy_test)

          • 部署生產(chǎn)服務(wù)器(deploy_production)

          注意:設(shè)置 Job.only 后,只有當(dāng) develop 分支和 master 分支有提交的時(shí)候才會(huì)觸發(fā)相關(guān)的 Jobs。

          節(jié)點(diǎn)說(shuō)明:

          • stages:定義構(gòu)建階段,這里只有一個(gè)階段 deploy

          • deploy:構(gòu)建階段 deploy 的詳細(xì)配置也就是任務(wù)配置

          • script:需要執(zhí)行的 shell 腳本

          • only:這里的 master 指在提交到 master 時(shí)執(zhí)行

          • tags:與注冊(cè) runner 時(shí)的 tag 匹配

          測(cè)試集成效果

          所有操作完成后 push 代碼到服務(wù)器,查看是否成功:

          passed 表示執(zhí)行成功

          其它命令

          • 刪除注冊(cè)信息

          gitlab-ci-multi-runner unregister --name "名稱"
          復(fù)制代碼
          • 查看注冊(cè)列表

          gitlab-ci-multi-runner list
          復(fù)制代碼

          附:項(xiàng)目配置 Dockerfile 案例

          FROM openjdk:8-jre
          MAINTAINER Lusifer <[email protected]>
          ENV APP_VERSION 1.0.0-SNAPSHOT
          ENV DOCKERIZE_VERSION v0.6.1
          RUN wget https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz \
          && tar -C /usr/local/bin -xzvf dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz \
          && rm dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz
          RUN mkdir /app
          COPY myshop-service-user-provider-$APP_VERSION.jar /app/app.jar
          ENTRYPOINT ["dockerize", "-timeout", "5m", "-wait", "tcp://192.168.10.131:3306", "java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/app/app.jar"]
          EXPOSE 8501
          復(fù)制代碼

          持續(xù)集成實(shí)戰(zhàn)用戶管理服務(wù)

          部署通用模塊項(xiàng)目

          先將所有被依賴項(xiàng)目(通用模塊項(xiàng)目)部署到 Nexus,為項(xiàng)目創(chuàng)建一個(gè)deploy.bat文件,示例代碼如下:

          cd ..
          cd myshop-dependencies
          call mvn deploy
          cd ..
          cd myshop-commons
          call mvn deploy
          cd ..
          cd myshop-commons-domain
          call mvn deploy
          cd ..
          cd myshop-commons-mapper
          call mvn deploy
          cd ..
          cd myshop-commons-dubbo
          call mvn deploy
          cd ..
          cd myshop-static-backend
          call mvn deploy
          cd ..
          cd myshop-service-user-api
          call mvn deploy
          復(fù)制代碼

          持續(xù)集成依賴管理項(xiàng)目

          由于我們所有項(xiàng)目的父工程都是依賴于myshop-dependencies,所以我們持續(xù)集成的第一步是將該項(xiàng)目進(jìn)行持續(xù)集成,在項(xiàng)目目錄創(chuàng)建.gitlab-ci.yml文件,代碼如下:

          stages:
          - deploy
          deploy:
          stage: deploy
          script:
          - /usr/local/maven/apache-maven-3.5.3/bin/mvn clean install
          復(fù)制代碼

          持續(xù)集成服務(wù)提供者

          gitlab-ci.yml

          # 定義階段
          stages:
          - build
          - push
          - run
          - clean
          build:
          stage: build
          script:
          - /usr/local/maven/apache-maven-3.5.3/bin/mvn clean package
          - cp target/myshop-service-user-provider-1.0.0-SNAPSHOT.jar docker
          - cd docker
          - docker build -t 192.168.10.133:5000/myshop-service-user-provider:v1.0.0 .
          push:
          stage: push
          script:
          - docker push 192.168.10.133:5000/myshop-service-user-provider:v1.0.0
          run:
          stage: run
          script:
          - cd docker
          - docker-compose down
          - docker-compose up -d
          clean:
          stage: clean
          script:
          - docker image prune -f
          復(fù)制代碼

          Dockerfile

          FROM openjdk:8-jre
          MAINTAINER Lusifer <[email protected]>
          ENV APP_VERSION 1.0.0-SNAPSHOT
          ENV DOCKERIZE_VERSION v0.6.1
          RUN wget https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz \
          && tar -C /usr/local/bin -xzvf dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz \
          && rm dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz
          RUN mkdir /app
          COPY myshop-service-user-provider-$APP_VERSION.jar /app/app.jar
          ENTRYPOINT ["dockerize", "-timeout", "5m", "-wait", "tcp://192.168.10.131:3306", "java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/app/app.jar"]
          EXPOSE 8501 22222 20881
          復(fù)制代碼

          docker-compose.yml

          version: '3.1'
          services:
          myshop-service-user-provider:
          image: 192.168.10.133:5000/myshop-service-user-provider:v1.0.0
          container_name: myshop-service-user-provider
          ports:
          - 8501:8501
          - 22222:22222
          - 20881:20881
          networks:
          default:
          external:
          name: dubbo
          復(fù)制代碼

          持續(xù)集成服務(wù)消費(fèi)者

          gitlab-ci.yml

          stages:
          - build
          - push
          - run
          - clean
          build:
          stage: build
          script:
          - /usr/local/maven/apache-maven-3.5.3/bin/mvn clean package
          - cp target/myshop-service-user-consumer-1.0.0-SNAPSHOT.jar docker
          - cd docker
          - docker build -t 192.168.10.133:5000/myshop-service-user-consumer:v1.0.0 .
          push:
          stage: push
          script:
          - docker push 192.168.10.133:5000/myshop-service-user-consumer:v1.0.0
          run:
          stage: run
          script:
          - cd docker
          - docker-compose down
          - docker-compose up -d
          clean:
          stage: clean
          script:
          - docker image prune -f
          復(fù)制代碼

          Dockerfile

          FROM openjdk:8-jre
          MAINTAINER Lusifer <[email protected]>
          ENV APP_VERSION 1.0.0-SNAPSHOT
          ENV DOCKERIZE_VERSION v0.6.1
          RUN wget https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz \
          && tar -C /usr/local/bin -xzvf dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz \
          && rm dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz
          RUN mkdir /app
          COPY myshop-service-user-consumer-$APP_VERSION.jar /app/app.jar
          ENTRYPOINT ["dockerize", "-timeout", "5m", "-wait", "tcp://192.168.10.131:20881", "java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/app/app.jar"]
          EXPOSE 8601 8701
          復(fù)制代碼

          docker-compose.yml

          version: '3.1'
          services:
          myshop-service-user-consumer:
          image: 192.168.10.133:5000/myshop-service-user-consumer:v1.0.0
          container_name: myshop-service-user-consumer
          ports:
          - 8601:8601
          - 8701:8701
          networks:
          default:
          external:

                  name: my_net

          END


          瀏覽 40
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  天天躁日日躁精品人妻 | 天天操天天撸视频免费 | 中文字幕亚洲综合 | 97国产超碰免费 | 亚洲一区二区在线免费观看 |