## 開發環境 Docker 化 ## & ## GitLab CI ###### 投影片: [hackmd.io/@taichunmin/r1E6Kl6_z](https://hackmd.io/@taichunmin/r1E6Kl6_z) ###### [#Agile.Taichung](https://agile-taichung.kktix.cc/) Note: $('code').each(function () { let a = $(this).html(); $(this).html(a.replace(/&amp;amp;/g, '&amp;')); }) --- ### Who am I? * 戴均民 * 現職: 微程式 DevOps 組 * [COSCUP 2016 工作坊 講師](http://coscup.org/2016/schedules.html#H23) * https://github.com/taichunmin --- ## 真實案例 ### 大學社團教 git --- #### 問題 * 每個人都要有 git 環境 * 電腦有還原卡只能夠現場裝 * 網路很慢一起抓檔案絕對超慢 * 課堂時間有限沒時間裝環境 --- #### 於是就想用 Docker 來處理 * 用 docker 建立每個人獨立的環境 * 學弟用 ssh 連線軟體連線到 docker --- #### 該怎麽開很多 Docker 互相連接? * 需要在主機上開很多個 Docker * Docker 之間要能夠有網路互相連接 * 要能夠方便知道 SSH 的 Port --- ### `docker run --link` ? ![](https://i.imgur.com/hM1RFof.png) --- ## docker-compose #### 取代 `docker run` 的工具 #### 能夠 **快速** 開很多服務 #### 支援 `.env` 環境變數 --- ## DEMO ![](https://i.imgur.com/eZkuRyF.png) ---- ## 開啟一臺虛擬機器 * [DigitalOcean](https://cloud.digitalocean.com/login) * 系統: Ubuntu 16.04.3 x64 * 規格: USD$ 5/month * 地區: Singapore * Add SSH keys * 機器名稱 ---- ## 設定 DNS * [CloudFlare](https://www.cloudflare.com/a/login) * 複製 IP 填到 DNS 中 * 不要過 CloudFlare CDN ---- ## 設定 ssh config ``` Host do HostName do.taichunmin.tk User root IdentityFile ~/.ssh/taichunmin@gmail.com.key ``` ---- ## 安裝 git-it-docker ```shell git clone https://github.com/taichunmin/git-it-course-docker.git cd git-it-course-docker bash ./install.sh ``` --- ###### `docker-compose up -d` ###### 一次開啟多個 Docker 服務 --- ## DEMO ![](https://i.imgur.com/XTK2KSd.png) ###### `docker-compose up -d --scale client = 3` --- ## 常用指令 <ul> <li class="fragment" data-fragment-index="1"><code>docker-compose ps</code><br>查看 ports 和正在執行的服務</li> <li class="fragment" data-fragment-index="2"><code>docker-compose pull</code><br>更新 image</li> <li class="fragment" data-fragment-index="3"><code>docker-compose exec [服務] [指令]</code><br>執行指令</li> <li class="fragment" data-fragment-index="4"><code>docker-compose logs [服務]</code><br>查看 example 的 stdout</li> <li class="fragment" data-fragment-index="5"><code>docker-compose down</code><br>關閉機器</li> </ul> --- ### DEMO: 假設有人忘記 client 密碼 1. 使用 `docker-compose ps` 看第幾個 client 2. 使用 `docker-compose exec --index=2 client bash` 3. 執行 `passwd` 強制改密碼 4. `exit` 退出 --- ### 該怎麼寫 docker-compose <ol> <li class="fragment" data-fragment-index="1">規劃架構</li> <li class="fragment" data-fragment-index="2">定義 <code>Dockerfile</code> 建立<br>或是使用現有的 image</li> <li class="fragment" data-fragment-index="3">以 <code>docker-compose.yml</code><br>定義有什麼 Docker 服務</li> <li class="fragment" data-fragment-index="4">使用 <code>docker-compose up -d</code><br>來啟動機器</li> </ol> --- ## 架構 ```mermaid graph LR Dashboard --- |"讀寫資料庫<br />(backend)"| Redis Dashboard -.-> |"查看儀錶板<br />(Port 80)"| 瀏覽器(瀏覽器) Client-1 --> |"回報解題狀況<br />(frontend)"| Dashboard Client-2 --> |"回報解題狀況<br />(frontend)"| Dashboard Client-N --> |"回報解題狀況<br />(frontend)"| Dashboard SSH-1(SSH-1) -.-> |"Port 32768"| Client-1 SSH-2(SSH-2) -.-> |"Port 32769"| Client-2 SSH-N(SSH-N) -.-> |"Port N"| Client-N ``` Note: 學弟妹透過 ssh 軟體連線至 client client 每一段時間透過 frontend 網路溝通 dashboard 瀏覽器連線至 dashboard 查看儀錶板 dashboard 透過 backend 連線至 redis 讀寫資料 --- ## dashboard 的設定 ```yaml version: '2' services: dashboard: # 儀錶板 image: taichunmin/git-it-course-docker:dashboard networks: - frontend # 給瀏覽器和 client 連線用 - backend # 連線資料庫用 volumes: # 掛載外部的檔案到 docker 容器中 # 為了取得所有 client 對外的 SSH port - /var/run/docker.sock:/var/run/docker.sock # 儀錶板的程式碼 - ./webapp:/var/www/html # 登入 Shell 後的歡迎訊息 - ./dashboard/motd:/etc/motd:ro ports: # port 轉發設定 - "80:80" # 把主機的 80 對應到 docker 的 80 restart: always # 異常結束自動重啟 ``` --- ## client 的設定 ```yaml version: '2' services: client: # git-it 環境 image: taichunmin/git-it-course-docker:client networks: - frontend # 給 client 連線到 dashboard 用 depends_on: # 相依的服務,compose 會一起啟用這個服務 - dashboard volumes: # 登入 Shell 後的歡迎訊息 - ./client/motd:/etc/motd:ro ports: # port 轉發設定 - "22" # 把主機的隨機 port 對應到 docker 的 22 ``` --- ## redis 的設定 ```yaml version: '2' services: redis: # 使用 Docker Hub 上面提供的 redis image: redis:latest networks: - backend # 給 dashboard 連線用 restart: always # 異常結束自動重啟 ``` --- ### docker-compose 的服務 ### 該怎麼連線到其他服務? --- #### 只要使用 `docker-compose.yml` #### 裡面的服務名稱就可以連線了 --- ### dashboard 連線到 redis ![](https://i.imgur.com/csPrgYP.png) --- ### client 連線到 dashboard ![](https://i.imgur.com/uHWyYxV.png) --- ## DEMO #### 多個 client 會如何? ```shell # docker-compose up -d --scale client=5 # docker-compose exec dashboard bash apt-get update apt-get install -y dnsutils dig +short client ``` --- # Dockerfile <ol> <li class="fragment" data-fragment-index="1">Dockerfile 裡面就是創建<br>image 所需要的全部步驟</li> <li class="fragment" data-fragment-index="2">我們可以使用 <code>docker build</code><br> 來創建映像檔</li> </ol> --- ## 怎麽寫 Dockerfile <ol> <li class="fragment" data-fragment-index="1">選擇從哪個 image 繼承</li> <li class="fragment" data-fragment-index="2">安裝所需軟體 (需改成不會詢問的指令)</li> <li class="fragment" data-fragment-index="3">修改設定檔</li> <li class="fragment" data-fragment-index="4">設定容器的啟動指令</li> </ol> --- ## 寫 Dockerfile 的小訣竅 * 先開啟你所繼承的 image<br>`docker run -it ubuntu:latest bash` * 手動執行指令來安裝環境<br>再把指令寫入 `Dockerfile` 裡面 --- ## client 的 Dockerfile * [client 的 Dockerfile](https://github.com/taichunmin/git-it-course-docker/blob/master/client/Dockerfile) * [dashboard 的 Dockerfile](https://github.com/taichunmin/git-it-course-docker/blob/master/dashboard/Dockerfile) --- ## Dockerfile ## [Best Practices](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/) --- ## 使用 `.dockerignore` * `docker build` 會把目前目錄丟給 dockerd * 語法跟 `.gitignore` 一樣<br>避免把沒用的檔案傳給 dockerd --- ### 避免安裝沒用到的軟體 --- ### 一個容器最好只有一個用途 * 例如: 資料庫、網站服務 應該分開 * 增加容器重複利用的可能性 * 使用 docker network 來互相連接 --- ## 對指令的多行參數排序 * 如 apt-get 將每個套件獨立一行 ```Dockerfile RUN apt-get update && apt-get install -y --no-install-recommends \ curl \ git \ nano \ openssh-server \ vim ``` --- ## 關於 FROM * 盡量使用 Docker Hub 官方維護的 image<br><br>例: 一個 node.js 寫的程式<br>最好從 `node:latest` 繼承<br>而不是從 `ubuntu` 繼承 --- ## 關於 FROM * 想要把 image 最小化<br>從 [Alpine image](https://hub.docker.com/_/alpine/) 繼承就對了<br>(目前這個 image 小於 5 MB)<br>很多官方 image 都有 alpine 版本 --- ## 關於 LABEL * 在 Docker 1.10 以前務必把所有 LABEL 寫在同一行 * 在 Docker 1.10 以後分開寫比較好看 --- ## 關於 RUN * 盡量將 RUN 的指令寫在一起<br>避免產生多餘的 layer * 用反斜線 `\` 換行,增加可讀性 --- ## 關於 apt-get * 務必將 `apt-get update`<br>和 `apt-get install -y` 寫在同一行<br>避免 `apt-get update` 誤用 cache ```Dockerfile RUN apt-get update && apt-get install -y \ package-bar \ package-baz \ package-foo ``` --- ## 關於 apt-get * 確保 `apt-get install -y` 或類似的指令<br>要加上 `-y` 這種自動安裝不詢問的選項 * 用完 `apt-get` 後記得刪除多餘的檔案<br>`rm -rf /var/lib/apt/lists/*` * 由於官方的 Debian 和 Ubuntu 已經會<br>[幫你在 docker 執行 `apt-get clean`](https://github.com/moby/moby/blob/03e2923e42446dbb830c654d0eec323a0b4ef02a/contrib/mkimage/debootstrap#L82-L105)<br>所以可以省略 --- ## 關於 apt-get ```Dockerfile RUN apt-get update && apt-get install -y \ aufs-tools \ automake \ build-essential \ curl \ dpkg-sig \ libcap-dev \ libsqlite3-dev \ mercurial \ reprepro \ ruby1.9.1 \ ruby1.9.1-dev \ s3cmd=1.1.* \ && rm -rf /var/lib/apt/lists/* ``` --- ## 關於 CMD * 盡量用陣列語法<br>`CMD ["apache2","-DFOREGROUND"]` --- ## 關於 EXPOSE * 應該用每個服務預設的 port - 例如 Apache 是 80,Mongo 是 27017 * 避免其他 image 在連接上出問題<br>因為 link 會幫你加類似這樣的環境變數<br>`MYSQL_PORT_3306_TCP` --- ## 關於 ENV * 把可執行檔透過 ENV 設定至 `$PATH` 中 * 把軟體版本指定至 ENV 中 ```Dockerfile ENV PG_MAJOR 9.3 ENV PG_VERSION 9.3.4 ``` --- ## 關於 ADD 或 COPY * 由於 `ADD` 會有一些額外的行為<br>所以能用 `COPY` 就不要用 `ADD` * `ADD` 會幫你解壓縮 linux 的壓縮檔<br>(只支援 `gzip`, `bzip2` 或 `xz`)<br>(對! 就是沒有 `zip`) --- ## 關於 VOLUMN * 下列檔案應該使用 VOLUMN 寫出來 * 資料庫的資料存放區 * 服務的設定檔 * 服務執行過程會建立的檔案<br>(log, cache) * 由於虛擬的檔案系統效能沒有 VOLUMN 好<br>應該盡量使用 VOLUMN --- ## 關於 WORKDIR * 應使用 WORKDIR 避免一直用 `cd` 切換資料夾 ```Dockerfile RUN cd … && do-something ``` --- ## GitLab CI ![](https://i.imgur.com/qaIq8Xd.png) --- ### 由於很多公司都喜歡用 GitLab --- #### 所以跟 GitLab 整合度很高的 #### GitLab CI 是一個很棒的選擇 --- ## 你只要在你的專案中 --- ## 新增 `.gitlab-ci.yml` --- ## 然後再新增 Runner --- ## 就能夠跑 CI/CD 了 --- ### 讓 GitLab CI ### 幫我 build Dockerfile ### 並且放到 GitLab Registry ### [docker-compose-git](https://gitlab.com/taichunmin/docker-compose-git) --- ## Dockerfile ```Dockerfile FROM docker:latest LABEL maintainer="taichunmin@gmail.com" RUN apk --no-cache add bash git openssh py2-pip && \ pip install docker-compose ``` --- ## 加上 `.gitlab-ci.yml` ```yaml image: docker:latest variables: IMAGE_TAG: "registry.gitlab.com/taichunmin/docker-compose-git:latest" services: - docker:dind stages: - build before_script: - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY build: stage: build tags: - docker script: - docker build -t $IMAGE_TAG . - docker run --rm $IMAGE_TAG docker-compose -v - docker run --rm $IMAGE_TAG git --version - docker push $IMAGE_TAG ``` ---- ## image & services * image 是主要的執行環境<br>GitLab CI 會幫你把程式碼下載到 WORKDIR * services 則是需要 link 到<br>主要執行環境的 docker image<br>但是裡面不會有程式碼<br>通常拿來是掛資料庫使用 --- ## stages 代表這份 GitLab CI 要跑那些階段 ```yaml stages: - build XXX_job: # 這行 job 的名字單純辨識用 stage: build ``` --- ## before_script 設定所有 job 執行前所需執行的共通指令 ```yaml before_script: - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY ``` * `$CI_REGISTRY_USER`,<br>`$CI_REGISTRY_PASSWORD`,<br>`$CI_REGISTRY`<br>皆是 [GitLab CI 給予的環境變數](https://docs.gitlab.com/ce/ci/variables/) --- ## variables 自訂的變數,方便在 CI/CD 流程中使用<br>在此的 `IMAGE_TAG` 是 GitLab<br>內建的 Registry 所提供的 push 網址 ```yaml variables: IMAGE_TAG: "registry.gitlab.com/taichunmin/docker-compose-git:latest" ``` --- ## tags 指定 GitLab CI Runner 必須要有什麼 tag 所以我們挑 docker image 為基準的 Runner ```yaml XXX_job: tags: - docker ``` ![](https://i.imgur.com/RCh9xUM.png) --- ## script 指定 GitLab CI 要在這個 job 中執行什麼指令<br>只要有指令異常結束 (回傳值非 0)<br>則這個 job 就等同執行失敗 ```yaml build: script: - docker build -t $IMAGE_TAG . - docker run --rm $IMAGE_TAG docker-compose -v - docker run --rm $IMAGE_TAG git --version - docker push $IMAGE_TAG ``` --- ### 當你設定完 `.gitlab-ci.yml` 後 --- ### 每當你 commit + push --- ### 就會開始執行 CI --- ![](https://i.imgur.com/v1xsKCK.png) --- ## 結合 docker-compose ## 一次 build 多個 image --- ## 一個公司的新專案 --- ## 我把開發環境<br>全部都 docker 化了 --- #### 但是每次要 build Docker 都要很久 --- #### 於是就讓 GitLab CI 幫我 build --- #### 以下是這個專案的 Docker 部分節錄 [mp-minioasis-docker](https://gitlab.com/taichunmin/mp-minioasis-docker/) --- ## QA 時間 ## 請多指教
{"metaMigratedAt":"2023-06-14T15:51:08.457Z","metaMigratedFrom":"YAML","title":"開發環境 Docker 化 & GitLab CI","breaks":true,"slideOptions":"{\"transition\":\"slide\",\"theme\":\"moon\"}","contributors":"[{\"id\":\"0d9a5e06-1f92-4142-b9df-fed4c8873573\",\"add\":72,\"del\":67}]"}
    2068 views