# 包裝 Java War file with Tomcat to Docker Image ###### tags: `Tag(docker)` `Tag(Tomcat)` 這是拿 java war檔由淺入深的實作Docker的練習筆記 ### Agenda 包裝Docker/啟動Container的方式由簡入深: - 陽春版 - 加入Volume - 用docker volume 指定volume - 使用docker-compose啟動container - 指定container name - docker-compose 啟動container放到背景執行 - container指定時區 - tomcat升版 - 加入nginx --- ### 陽春版 2. a simple war, just show HelloWorld string on page ![xxx](https://i.imgur.com/F5Ji7uD.png) 3. Dockerfile ``` From tomcat:jre8-alpine ADD HelloWorldDocker.war /usr/local/tomcat/webapps/ EXPOSE 8080 CMD ["catalina.sh", "run"] ``` 3. Create a Folder, named javawar1 and put the war file and Dockerfile ```buildoutcfg javawar1 ├── Dockerfile └── HelloWorldDocker.war ``` 4. 建立Docker image指令 ```buildoutcfg % cd javawar1 % docker build --no-cache -t javawar1_image . [+] Building 3.8s (8/8) FINISHED => [internal] load build definition from Dockerfile 0.0s => => transferring dockerfile: 37B 0.0s => [internal] load .dockerignore 0.0s => => transferring context: 2B 0.0s => [internal] load metadata for docker.io/library/tomcat:jre8-alpine 3.6s => [auth] library/tomcat:pull token for registry-1.docker.io 0.0s => [internal] load build context 0.0s => => transferring context: 219B 0.0s => [1/2] FROM docker.io/library/tomcat:jre8-alpine@sha256:04feaf74f8bb54b43ea136b150bbc7b58e8a3062aead67ab871f2dbbd5dac5d1 0.0s => CACHED [2/2] ADD HelloWorldDocker.war /usr/local/tomcat/webapps/ 0.0s => exporting to image 0.0s => => exporting layers 0.0s => => writing image sha256:fd9718cd200606846cf5854cdcb04d5cdb2c658a2a75e21c3fa411037c1125c0 0.0s => => naming to docker.io/library/javawar1_image ``` - 指定--no-cache 可避免cache - -t image命名為 javawar1_image 指定版號 -t javawar1_image:v2 5. 啟動container ```buildoutcfg docker run -p 80:8080 javawar1_image ``` 6. 確認服務 開啟瀏覽器 輸入 http://localhost/HelloWorldDocker/HelloWorld ![](https://i.imgur.com/fR4iCi8.png) --- ### 加入Bind Mount - 上述啟動服務後,透過 Docker Dashboard 可直接點選該container CLI ![](https://i.imgur.com/gUkO5sP.png) - 它會帶著docker exec -it {完整container_id} /bin/sh 進入container,就可以對container下linux指令。預設路徑就是tomcat工作路徑 ![](https://i.imgur.com/Mn8Gw3R.png) container內tomcat 在/usr/local/tomcat/logs 下產生log - 緣由: - 若執行docker run,每次都起新的container,這些log就消失(應該要改用docker start *containerid* 來啟動已存在的container) - 要到container內找log不方便,可以指定本機目錄取代container內的/usr/local/tomcat/logs。 (ps.查閱log應可結合其他監控軟體,後續再延伸。) - 應用程式除了Log之外,如資料庫還是需要有固定存取的磁碟空間 - 作法: - 先在本機新增一個目錄,在本機的路徑是 /Users/dancyu/codes/docker_practice_private/volume/javawar1 - 這是啟動container時的方法要調整,不需重做image 故不用修改Dockerfile - docker run執行 ```buildoutcfg docker run -p 80:8080 -v /Users/dancyu/codes/docker_practice_private/volume/javawar1:/usr/local/tomcat/logs javawar1_image ``` 執行起來後,可以看到本機的目錄已有tomcat產生的檔案 ![](https://i.imgur.com/mx6PTaD.png) (這是新啟動的container所以不會有之前寫在container內的log) --- ### 用docker volume 指定volume 除了bind mount,透過docker 管理volume,只有當容器啟動時才能透過容器去看volume內容 - 指令格式 ```docker volume create [OPTIONS] [VOLUME]``` - 指令 ```buildoutcfg % docker volume create javawar1_vol javawar1_vol 觀察docker volume: % docker volume inspect javawar1_log [ { "CreatedAt": "2021-11-16T03:17:36Z", "Driver": "local", "Labels": {}, "Mountpoint": "/var/lib/docker/volumes/javawar1_vol/_data", "Name": "javawar1_vol", "Options": {}, "Scope": "local" } ] ``` - docker run執行 ```buildoutcfg docker run -p 80:8080 -v javawar1_vol:/usr/local/tomcat/logs javawar1_image ``` - 關於docker volume create 官網文件可參考此[連結](https://docs.docker.com/engine/reference/commandline/volume_create/) - 如下圖是 container運行時在存儲上有三類方法 - bind mount : 與主機的Filesystem直接鏈結 - volume : 透過Docker 管理,資料只能在容器運行時掛載volume才能進容器看到 - tmpfs mount : a tmpfs mount is temporary, and only persisted in the host memory. When the container stops, the tmpfs mount is removed, and files written there won’t be persisted. 其限制包含容器間也不能共享。 ![]( https://docs.docker.com/storage/images/types-of-mounts.png) --- ### 改用docker compose啟動container 延續上一個專案,將docker run 改為 docker-compose的方法 - 在javawar1目錄下新增 docker-compose.yml - bind mount 方式 ``` version: '3.7' services: web: container_name: javawar1_app image: javawar1_image volumes: - /Users/dancyu/codes/docker_practice_private/volume/javawar1:/usr/local/tomcat/logs - /etc/localtime:/etc/localtime:ro ports: - 80:8080 ``` - volume 方式 ```buildoutcfg version: '3.7' services: web: image: javawar1_image volumes: - javawar1_vol:/usr/local/tomcat/logs ports: - 80:8080 volumes: javawar1_vol:{javawar1_vol} ``` - docker-compose.yml - version 是指docker-compose yml格式版本,不是應用程式的版本,應用程式的版本可以定義在Dockerfile LABEL內。 - image 指定使用產生的image,若不指定image 要改用build: . (docker_compose會參照同目錄下的Dockerfile重新build image再啟動container) - 執行 ```buildoutcfg docker-compose up ``` 啟動後會發現 stack 會以執行位置的當前目錄來命名, ![](https://i.imgur.com/qFxc3Nx.png) 關閉container後,再次執行docker-compose up也是同一個container 另外 原來預期改用docker-compose.yml指定volume,應該要使用同一塊磁碟,但並沒有,使用docker dashboard inspect javawar1_web_1的內容 發現Mount目錄名為javawar1_javawar1_vol ![](https://i.imgur.com/3TFfGm2.png) 透過docker dashboard看Volumes, 發現 docker run 與docker-compose確實都使用不同volumes,為了確認命名,我又多建了一個javawar2目錄,docker-compose.yml 把port改為81:8080,確定新增了一個javawar2_javawar1_vol ![](https://i.imgur.com/mF6WoO7.png) - 這裡再多做一個小實驗,驗證多個container可以指向同一塊volume,同時起tomcat,用不同port來提供服務 ``` % docker run -p 82:8080 -v javawar1_vol:/usr/local/tomcat/logs javawar1_image % docker run -p 83:8080 -v javawar1_vol:/usr/local/tomcat/logs javawar1_image ``` - 確定container們都寫了同一個access file,多個container可以指向同一塊volume ![](https://i.imgur.com/vXG0Gz6.png) - 結論: 1. volume 可以同時給多個container同時使用 * 注意在這個實驗拿tomcat當範例很不適合,正式環境不會把多個tomcat log導在一起, 本範例只是在熟悉volume為目的而實作。 3. docker run 與docker-compose 啟動的container 指定volume時要注意目錄名 (但是否為我的docker-compose指定的不正確,未正確設定volumes map內容,待考證) --- ### 指定container name 在docker-compose.yml加上 container_name ``` version: '3.7' services: web: container_name: javawar1_app image: javawar1_image volumes: - javawar1_vol:/usr/local/tomcat/logs ports: - 80:8080 volumes: javawar1_vol: ``` 指定container name之後,如下 ![](https://i.imgur.com/ZxpqBAD.png) docker exec -it 也可指定container_name,不用去找container id ![](https://i.imgur.com/P4sHdyX.png) ### 啟動container放到背景執行 執行docker-cpmpose up 加上-d 背景執行,就不會每次都跑出log ``` docker-compose up -d ``` --- ### container指定時區 tomcat的log內容都是utc時區, 非CST 在docker-compose.yml加上 ```buildoutcfg version: '3.7' services: web: image: javawar1_image volumes: - javawar1_vol:/usr/local/tomcat/logs ports: - 80:8080 environment: - TZ=Asia/Taipei volumes: javawar1_vol:{javawar1_vol} ``` 更改後,再用docker-compose up執行,發現log寫出的時間已是台北時區 ![](https://i.imgur.com/98NrgBw.png) 但是時間還是UTC,google其他同步container與本機時間的方式,但沒有效果。 -v /etc/localtime:/etc/localtime:ro ```buildoutcfg version: '3.7' services: web: image: javawar1_image volumes: - javawar1_vol:/usr/local/tomcat/logs - /etc/localtime:/etc/localtime:ro ports: - 80:8080 environment: - TZ=Asia/Taipei volumes: javawar1_vol:{javawar1_vol} ``` 思考:監控軟體抓取log的時間可以讀取utc並顯示為 cst, 或許可不去改container時間。目前還在嘗試單一docker的階段,設定TZ=Asia/Taipei已夠debug用。(關於container date時區顯示正確性以後再深究) --- ### Tomcat 升版 1. 修改Dockerfile ``` From tomcat:9-jdk8 ADD HelloWorldDocker.war /usr/local/tomcat/webapps/ EXPOSE 8080 CMD ["catalina.sh", "run"] ``` 2. 修改docker-compose.yml ``` version: '3.7' services: web: build: . container_name: javawar1_app image: javawar1_image volumes: - /Users/dancyu/codes/docker_practice_private/volume/javawar1:/usr/local/tomcat/logs - /etc/localtime:/etc/localtime:ro ports: - 80:8080 environment: - TZ=Asia/Taipei ``` (為了debug 查tomcat log 方便,這裏選用bind mount的方式) 3. 執行 ``` docker-compose up --build ``` 啟動後確認http://localhost/HelloWorldDocker/HelloWord 可work,inspect javawar1_app內容也符合預期Tomcat取得9的最新版本 ![](https://i.imgur.com/4qopE0T.png) (production應該指定版本比較恰當) --- ### 加入Nginx 新增另一個目錄web_war來實作增加nginx 架構的變化可參考如下 ![](https://i.imgur.com/qxuZjkl.png) web_war的目錄結構如下: ``` web_war ├── docker-compose.yml ├── nginx │   └── nginx.conf ├── tomcatlog └── weblog ``` - tomcatlog 與 weblog 是目錄 - docker-compose.yml ``` version: '3.7' services: web: # restart: on-failure image: nginx volumes: - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf - /Users/dancyu/codes/docker_practice_private/web_war/weblog:/app/static ports: - 80:80 depends_on: - war environment: - TZ=Asia/Taipei war: image: javawar1_image # restart: always volumes: - /Users/dancyu/codes/docker_practice_private/web_war/tomcatlog:/usr/local/tomcat/logs environment: - TZ=Asia/Taipei ``` - nginx/nginx.conf 映射到container中作為nginx的設定 ``` upstream javawar1 { server war:8080; } server { listen 80; location / { proxy_pass http://javawar1; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $host; proxy_redirect off; } location /static/ { alias /app/static/; } } ``` 1. 在web_war目錄下執行docker-compose up -d,可見服務啟動 ![](https://i.imgur.com/OGYMwVv.png) 2. 使用http://127.0.0.1/HelloWorldDocker/HelloWorld驗證OK 3. 這個目錄沒有放Dockerfile是因為dr-compose.yml直接引用在本文章節一 陽春版 所建立的javawar1 image,若docker-compose.yml有指定-build . 則需要把Dockerfile跟yml檔放在一起。 --- ### 待研究 1. docker run 或docker-compose up執行起container就直接看到tomcat log, 是否都應該改為背景執行? (closed) > docker-compose up -d > docker run -p 80:8080 -d javawar1_image > 指定-d 就會是背景執行了 3. 如何指定啟動都使用同一個container? (closed) > 因為docker run會產生一個新的container,要啟動同一個container 應該改用docker start 指令 > 但是以實務上,應保持conainer stateless的特性,我們需要的是服務存儲下資料的延續性,而非container是不是同一個。 4. docker-compose.yml volumes 的設定方式待確認 {}的設定方法 5. war file升版的處理流程 (closed) > 程式版本的部分將在 Springdocker 的練習筆記繼續演練 7. tomcat upgrade (closed) > 可參考章節“Tomcat 升版” 9. application container 應該是stateless的使用,隨時可以重啟AAA橫向擴充. 需要再研究 application的異常監控機制 10. Dockerfile內寫了cmd, docker-compose.yml也可以下EXEC 這兩個在實務上要怎麼搭配使用,需要再深入了解兩者間的差異。 (closed) > cmd 可以在docker run時被override. Dockerfile應該定義的是Entrypoint,也就是使用這個image的時候執行指令。 > 例如tomcat image服務是執行catalina.sh啟動服務 11. 嘗試升級tomcat版本時 發現tag很多,該怎麼選適合的? (closed) > 找到一些說明,有興趣的人可參考這篇[docker镜像Alpine, Slim, Stretch, Buster, Jessie, Bullseye, windowsservercore区别](https://www.timiguo.com/archives/223/) > 我目前是實驗階段,選擇最小的alpine就夠用了