本文將先介紹Development Container,然後示範如何透過VSCode Dev Container來打造Quarkus的容器式開發環境,包括Java、Python、Node.js、Go等程式語言都能夠使用此方式來開發系統,並且一個專案一個容器,不會污染本機環境,可以安心地執行作業。
在本刊168期(2020年1月)曾介紹過Red Hat所發展的雲原生Java框架Quarkus(https://quarkus.io/),尤其是整合Oracle GraalVM技術,編譯成Native Image(二進制機器碼),移除掉JVM這層中間碼轉譯,服務占用的記憶體更少,啟動時間更短,讓Java語言在容器化和雲端環境上持續保持在微服務開發上的優勢。而此文將介紹使用VSCode Dev Container(https://github.com/microsoft/vscode-dev-containers)打造Quarkus的容器式開發環境,不只有Java語言,還包括了Python、Node.js、Go等等,皆可使用此方式開發系統,且一個專案一個容器,不會污染本機環境。
何謂Development Container
近年,Web化的IDE開發工具如雨後春筍般地出現,進而發展出各種Cloud IDE平台,除了早期已被AWS併購的Cloud9,微軟也推出GitHub Codespaces服務,甚至連Eclipse基金會也發布了雲端和桌面都可使用的Eclipse Theia(https://theia-ide.org/)。
目前,雲端IDE平台服務皆採用容器化開發環境,除了可因應多元的開發語言以及框架之外,更具備了秒級啟動的使用者體驗,例如圖1所示的Gitpod就是最佳的案例。
微軟Visual Studio Code是目前成長最快的IDE,圖2是PYPL熱門指數(https://pypl.github.io/IDE.html);它是基於Google Trends的統計數據,Top IDE Index是PYPL透過分析用戶在Google上搜尋不同IDE的次數所產製的排名,此榜單可讓開發者用於參考決定選用哪個主流IDE進行軟體專案開發,正好此熱門指數也包含Online IDE, ODE(https://pypl.github.io/ODE.html)。
正因如此,VSCode擁有相當豐富的Extension套件,而微軟推出的Remote - Containers就是讓開發者可以自行配置設計的全功能容器化開發環境(https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)。Visual Studio Code Remote - Containers具備下列幾項優點:
‧使用一致且易於重製的開發工具鏈,可部署在同一作業系統上,彼此不會相互影響,如同時開發Python 2跟Python 3的環境問題。
‧在不同的容器化隔離開發環境之間快速切換,並放心且安全地更新升級,完全不必擔心會影響本機作業環境。
‧讓開發團隊新成員或外包廠商更快更輕鬆地在一致的開發環境中啟動並執行,不再有他的電腦可以,我的卻不行之窘境。
‧試用新技術或Clone開源程式庫之範例,無須異動或影響到本機配置,對RD人員或技術架構師特別有幫助。
此Remote - Containers套件利用Docker容器作為一個完整功能的開發環境,將專案程式掛載到容器內,且Visual Studio Code上全部功能和Extension套件皆可使用,透過名為devcontainer.json文件來配置專案所需要的Dockerfile、檔案目錄掛載和所需之Extension套件,其架構如圖3所示,而devcontainer.json範例可參考微軟Try Out Development Containers: Java專案(https://github.com/microsoft/vscode-remote-try-java)或Dev Container群組(https://github.com/dev-container)。
建立Quarkus框架的容器化開發環境
接下來,便以Quarkus框架來示範,首先可利用Maven或Gradle專案產生器網頁(https://code.quarkus.io/),或透過mvn、gradle CLI產生專案,但這需要在本機安裝,為了避免污染本機環境,開啟專案產生器網頁,直接按下圖4中的〔Generate your application〕按鈕,選擇「Download as a zip」。解壓縮之後,使用VSCode開啟「code-with-quarkus」目錄。
首先,記得安裝Docker和Remote - Containers套件,點擊在VSCode左下角的><圖案的狀態條(圖5),選擇「Open Folder in Container」(圖6),選擇Java和Version 11,並選取Install Maven後,按下OK,就可將「code-with-quarkus」專案執行在容器開發環境中,此時左下角的狀態條便顯示為> 如此就可著手Java程式開發,且發現自動新增了.devcontainer目錄,裡面包含Dockerfile和devcontainer.json,查看Dockerfile,可以看到與前面提到的微軟Try Out Development Containers: Java專案是一樣的,先宣告ARG VARIANT="15",讓下一行FROM mcr.microsoft.com/vscode/devcontainers/java:0-${VARIANT}使用對應之Java版本,接下來的Maven和Gradle也是,判斷true/false後用sdkman來安裝:
ARG VARIANT="15" FROM mcr.microsoft.com/vscode/ devcontainers/java:0-${VARIANT} # [Option] Install Maven ARG INSTALL_MAVEN="false" ARG MAVEN_VERSION="" # [Option] Install Gradle ARG INSTALL_GRADLE="false" ARG GRADLE_VERSION="" RUN if [ "${INSTALL_MAVEN}" = "true" ]; then su vscode -c "umask 0002 && . /usr/ local/sdkman/bin/sdkman-init.sh && sdk install maven \"${MAVEN_VERSION}\""; fi \ && if [ "${INSTALL_GRADLE}" = "true" ]; then su vscode -c "umask 0002 && . /usr/ local/sdkman/bin/sdkman-init.sh && sdk install gradle \"${GRADLE_VERSION}\""; fi # [Option] Install Node.js ARG INSTALL_NODE="true" ARG NODE_VERSION="lts/*" RUN if [ "${INSTALL_NODE}" = "true" ]; then su vscode -c "umask 0002 && . /usr/ local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi
接著查看devcontainer.json,name是此容器開發環境的名稱,而build是此容器環境該如何建立,其中包含dockerfile指出用哪個Dockerfile來產生,亦可改成image直接使用,args就是建立容器映像檔的參數,這是傳入前面談到的Java版本參數,及是否安裝Maven或Gradle等,而settings是宣告VSCode的變數設定,terminal.integrated.shell.linux是登入容器時使用何種Linux Shell,甚至可加入dotfile個人化終端環境(https://code.visualstudio.com/docs/remote/containers#_personalizing-with-dotfile-repositories),java.home是JDK的目錄位置,extensions是容器開發環境內使用之套件清單,forwardPorts是哪些Port要對應到本機Host,亦可加上Quarkus預設8080通訊埠,postCreateCommand則是容器啟動後要執行的指令,remoteUser是此容器開發環境的預設使用者(容器內必須有此使用者),若不宣告,就跟Docker一樣是root。
更多的詳細說明,請參考devcontainer.json reference(https://code.visualstudio.com/docs/remote/devcontainerjson-reference),範例如下:
"name": "Java", "build": { "dockerfile": "Dockerfile", "args": { // Update the VARIANT arg to pick a Java version: 11, 15 "VARIANT": "11", // Options "INSTALL_MAVEN": "true", "INSTALL_GRADLE": "false", "INSTALL_NODE": "false", "NODE_VERSION": "lts/*" } }, // Set *default* container specific sett ings.json values on container create. "settings": { "terminal.integrated.shell.linux": "/ usr/bin/zsh", "java.home": "/docker-java-home" }, // Add the IDs of extensions you want installed when the container is created. "extensions": [ "vscjava.vscode-java-pack" ], // Use 'forwardPorts' to make a list of ports inside the container available locally. //"forwardPorts": [8080], // Use 'postCreateCommand' to run commands after the container is created. // "postCreateCommand": "java -version", // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/ containers/non-root. "remoteUser": "vscode"
但對於Spring或Quarkus框架,還需要額外的Extension套件才能讓開發人員更方便,按下左邊功能列的Extension圖示,輸入Quarkus,找到合適套件後,按下右鍵,再點選【Add to devcontainer.json】,如圖8所示,就會加入到devcontainer.json的套件清單。
使用Docker in Docker產生專案映像檔
開啟「GreetingResource.java」檔案,插入debug中斷點,按下〔F1〕,一樣輸入quarkus,選擇「Quarkus: Debug current Quarkus project」,就如同在本機環境相同的體驗方式來除錯,開啟網址「http://localhost:8080/hello-resteasy」就進入Breakpoint,但是當專案需要部署到容器平台時,需要包裝成容器映像檔,Quarkus提供Jib、Docker和S2I(https://quarkus.io/guides/container-image),個人還是偏好使用Docker build,但是在容器開發環境,並沒有docker cli,故須先安裝docker-ce-cli,而微軟vscode-dev-containers專案中已有談到如何在開發容器中安裝Docker(https://github.com/microsoft/vscode-dev-containers/tree/master/containers/docker-from-docker)。
首先,開啟.devcontainer/Dockerfile,加上RUN執行apt指令來安裝,詳細內容如下:
# Install Docker CE CLI RUN apt-get update \ && apt-get install -y apt-transport- https ca-certificates curl gnupg2 lsb- release \ && curl -fsSL https://download.docker. com/linux/$(lsb_release -is | tr '[:upper:]' '[:lower:]')/gpg | apt-key add - 2>/dev/null \ && echo "deb [arch=amd64] https:// download.docker.com/linux/$(lsb_release -is | tr '[:upper:]' '[:lower:]') $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/ docker.list \ && apt-get update \ && apt-get install -y docker-ce-cli
如果想要讓non-root可使用Docker,無需sudo,則還要新增user並授權,可自行參閱Enabling non-root access to Docker in the container,若嫌上述步驟過於繁瑣,可直接使用安裝Script腳本,放到.devcontainer/library-scripts,詳細操作方式請參考Docker-from-Docker Install Script(https://github.com/microsoft/vscode-dev-containers/blob/master/script-library/docs/docker.md)。
接著須在.devcontainer/devcontainer.json,加上runArgs和mount volume,將本機端的docker.sock掛載到開發容器中,設定如下:
"runArgs": ["--init"], "mounts": ["source=/var/run/docker. sock,target=/var/run/docker. sock,type=bind"]
其中runArgs是docker run後面的參數,目的在確保init程序執行在PID 1,避免容器停止或移除時,exec/fork出來的process沒有正常停止,變成zombie processes。
詳細解釋可參考DOCKER DEMONS: PID-1, ORPHANS, ZOMBIES, AND SIGNALS(https://www.fpcomplete.com/blog/2016/10/docker-demons-pid1-orphans-zombies-signals/),中譯網址為https://www.mdeditor.tw/pl/g2ol/zh-hk。
配置完成後,執行Reopen in Container,或重新開啟VSCode,選擇File->Open Recent->code-with-quarkus [Dev Container],便會出現Rebuild詢問按鈕,重新建置後就有docker指令可使用,而Quarkus專案本身有提供Dockerfile,放置在「src/main/docker」目錄,執行「docker build -f src/main/docker/Dockerfile.jvm -t quarkus/code-with-quarkus .」,就可產製此專案容器映像檔(圖9)。
進行遠端除錯
建置完成執行專案容器,輸入「docker run -i --rm -p 8080:8080 quarkus/code-with-quarkus」,便可看到「Hello RESTEasy」畫面,但資訊系統若採微服務架構會有大量的相依專案,不可能全部都執行在本機環境,因此在遠端容器平台上除錯就能迅速找到錯誤,而Quarkus也支援Remote Development Mode和Remote Debugging,這兩者差異在於,當本地端程式偵測到有異動時,以Remote Development Mode啟動的遠端程式會自動更新並載入,無須重新建立Jar檔或映像檔,而Remote Debugging是透過JDWP(Java Debug Wire Protocol)讓IDE Client跟遠端Server-side溝通。
依照Quarkus - Building Applications with Maven(https://quarkus.io/guides/maven-tooling#remote-development-mode),若須啟用Remote Development Mode功能,必須打包成mutable-jar格式,步驟如下:
1. 執行「./mvnw clean package -Dquarkus.package.type=mutable-jar」。
2. 修改src/main/docker目錄底下的Dockerfile.fast-jar,加上ENV JAVA_ENABLE_DEBUG="true" QUARKUS_LAUNCH_DEVMODE="true",並在ENV JAVA_OPTIONS環境變數,加上-Dquarkus.package.type=mutable-jar -Dquarkus.live-reload.password=123,完整範例如下:
FROM registry.access.redhat.com/ubi8/ubi- minimal:8.3 ARG JAVA_PACKAGE=java-11-openjdk- headless ARG RUN_JAVA_VERSION=1.3.8 ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' # Install java and the run-java script # Also set up permissions for user `1001` RUN microdnf install curl ca-certificates ${JAVA_PACKAGE} \ && microdnf update \ && microdnf clean all \ && mkdir /deployments \ && chown 1001 /deployments \ && chmod "g+rwX" /deployments \ && chown 1001:root /deployments \ && curl https://repo1.maven.org/maven2/ io/fabric8/run-java-sh/${RUN_JAVA_ VERSION}/run-java-sh-${RUN_JAVA_VERSION}- sh.sh -o /deployments/run-java.sh \ && chown 1001 /deployments/run-java.sh \ && chmod 540 /deployments/run-java.sh \ && echo "securerandom.source=file:/dev/ urandom" >> /etc/alternatives/jre/lib/ security/java.security ENV JAVA_ENABLE_DEBUG="true" QUARKUS_ LAUNCH_DEVMODE="true" # Configure the JAVA_OPTIONS, you can add -XshowSettings:vm to also display the heap size. ENV JAVA_OPTIONS="-Dquarkus.http. host=0.0.0.0 -Djava.util.logging. manager=org.jboss.logmanager.LogManager -Dquarkus.package.type=mutable-jar -Dquarkus.live-reload.password=123" # We make four distinct layers so if there are application changes the library layers can be re-used COPY --chown=1001 target/quarkus-app/lib/ /deployments/lib/ COPY --chown=1001 target/quarkus-app/*. jar /deployments/ COPY --chown=1001 target/quarkus-app/app/ /deployments/app/ COPY --chown=1001 target/quarkus-app/ quarkus/ /deployments/quarkus/ EXPOSE 8080 USER 1001 ENTRYPOINT [ "/deployments/run-java.sh" ]
3. 執行「docker build -f src/main/docker/Dockerfile.fast-jar -t quarkus/code-with-quarkus:debug .」,便完成支援Remote Development Mode的專案容器。
4. 啟動此容器,輸入「docker run -d -p 8080:8080 -p 5005:5005 --name debug quarkus/code-with-quarkus:debug」,若通訊埠8080被占用,就改成對應8081。
5. 回到容器開發環境,在VSCode上開啟.vscode/launch.json,複製Debug Quarkus application此配置物件,改為Remote Debugging Quarkus application,並將hostName改成電腦IP位址,修改結果如下:
{ "version": "0.2.0", "configurations": [ { "preLaunchTask": "quarkus:dev", "type": "java", "request": "attach", "hostName": "localhost", "name": "Debug Quarkus application", "port": 5005 }, { "preLaunchTask": "quarkus:remote-dev", "type": "java", "request": "attach", "hostName": "", "name": "Remote Debugging Quarkus application", "port": 5005 } ] }
6. 開啟.vscode/task.json,因上個步驟使用到preLaunchTask,必須新增quarkus:remote-dev相對應的指令,一樣複製quarkus:dev的配置物件,label改成quarkus:remote-dev,command加上-Ddebug=false -Dquarkus.package.type=mutable-jar -Dquarkus.live-reload.url=http://:8080 -Dquarkus.live-reload.password=123,而background的endsPattern也需要改成^.*Quarkus augmentation completed in *,才能正確偵測到遠端程式啟動完畢,完整內容如下:
{ "label": "quarkus:remote-dev", "type": "shell", "command": "./mvnw quarkus:remote- dev -Ddebug=false -Dquarkus.package. type=mutable-jar -Dquarkus.live-reload. url=http://:8080 -Dquarkus.live- reload.password=123", "windows": { "command": ".\\mvnw.cmd quarkus:dev -Ddebug=false -Dquarkus.package. type=mutable-jar -Dquarkus.live-reload. url=http://:8080 -Dquarkus. live-reload.password=123" }, "isBackground": true, "problemMatcher": [ { "pattern": [ { "regexp": "\\b\\B", "file": 1, "location": 2, "message": 3 } ], "background": { "activeOnStart": true, "beginsPattern": "^.*Scanning for projects...*", "endsPattern": "^.*Quarkus augmentation completed in *" } } ] }
7. 最後,選擇「Remote Debugging Quarkus application」按下Run,便跟之前一樣進入Breakpoint,連線結果如圖10所示。 此時,因啟用Remote Development Mode,只要修改程式碼,例如將Hello RESTEasy改成Hello CathayHolding,便會自動判斷Client端是否有異動程式,並自動傳送到遠端Server內,連Remote Debugging的連線都不用重新建立,在API相互依賴的微服務開發環境中,可節省編譯部署的動作和時間,如圖11所示。
在K8s上遠端除錯
既然已可以用Docker容器來遠端除錯,必定也能應用到Kubernetes平台上,Quarkus稱為雲原生開發框架,也有支援Kubernetes的套件(https://quarkus.io/guides/deploying-to-kubernetes),按〔F1〕,輸入Quarkus,選擇「Quarkus: Add extensions to current project」,如圖12所示,完成後,在「src/main/resources/application.properties」檔案加上「quarkus.kubernetes.service-type=node-port」,讓接下來產生的部署檔採用NodePort方式對外提供服務,執行「./mvnw clean package」重新打包專案,完成後在target目錄中會產生kubernetes目錄和部署YAML/JSON檔案。開啟kubernetes.yaml,在有8080的參數,複製在下方後,改成5005,name改成debug,image改為quarkus/code-with-quarkus:debug,並記得docker push到Docker Hub,K8s的Pod才能正常Pull後啟動。
關於Kubernetes建置和部署YAML檔撰寫非本文介紹範圍,故此文略過安裝Kubernetes、Minikube和kubectl的步驟。
執行「kubectl apply -f kubernetes.yml」後,以「kubectl get svc」查詢對外的Port,如圖13所示,跟前面步驟類似,在VSCode的容器開發環境修改Remote Debugging的Port,launch.json和task.json,執行Run,再開啟「http://localhost:3xxxx/hello-resteasy」,就可直接對遠端的K8s進行程式碼除錯。
結語
以上示範後的Quarkus專案放置在GitHub(https://github.com/philipz/code-with-quarkus),而針對VSCode容器化開發環境,微軟亦提供線上教學,使用Visual Studio Code搭配Docker容器做為開發環境(https://docs.microsoft.com/zh-tw/learn/modules/use-docker-container-dev-env-vs-code/),建議延伸閱讀並練習。而其他進階使用,如Docker Compose和Remote Docker Host等,可參考Advanced Container Configuration說明(https://code.visualstudio.com/docs/remote/containers-advanced)。
示範完VSCode容器化開發環境的建置和Quarkus在Kubernetes平台上的程式Live Reload遠端除錯,可感受到雲原生時代的程式開發也開始Web化,除了Gitpod外,微軟Github Codespaces(https://github.com/features/codespaces)也是利用Dev Container技術,提供Online IDE服務,而線上容器化開發環境除了具備前面談到的四項優點外,受到COVID-19疫情影響,遠距工作讓企業加速佈局數位團隊工具。在Gartner研究報告Accelerating Agile and DevOps Adoption Post-COVID-19中,就談到雲端託管開發環境具備的靈活、共享和高可用之特點,可增加開發團隊的敏捷度及彈性,提供完整的程式碼版控、建置、測試和除錯功能,協助企業在後疫情時代下管控遠端開發及雲端遷移策略。而Quarkus框架,除了Native Image高效功能外,還支援微服務、事件驅動、響應式和公有雲服務等雲原生功能模組,而同樣服務於國泰金控數位架構部的架構師李琦(Rich Lee),也在2020 Java Community Conference Taiwan(https://jcconf.tw/2020/)—「Building cloud-native applications with Quarkus」演講中分享Quarkus使用心得,期望Red Hat Quarkus框架能持續解決微服務開發上的種種問題,並提高Java開發效率和容器平台的資源利用率。
<本文作者:鄭淳尹,Docker.Taipei社群共同發起人,國泰金控技術架構師,曾任微軟MVP、momo購物網架構師、臺北榮民總醫院資訊工程師、玉山銀行資訊處專員、宏碁eDC維運工程師,系統維護及開發設計超過16年。開源技術愛好者,曾在多間大學資工系擔任Docker容器技術講師,並翻譯審閱多本容器技術書籍。>