# 包裝 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

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

---
### 加入Bind Mount
- 上述啟動服務後,透過 Docker Dashboard 可直接點選該container CLI

- 它會帶著docker exec -it {完整container_id} /bin/sh 進入container,就可以對container下linux指令。預設路徑就是tomcat工作路徑

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產生的檔案

(這是新啟動的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. 其限制包含容器間也不能共享。

---
### 改用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 會以執行位置的當前目錄來命名,

關閉container後,再次執行docker-compose up也是同一個container
另外
原來預期改用docker-compose.yml指定volume,應該要使用同一塊磁碟,但並沒有,使用docker dashboard inspect javawar1_web_1的內容 發現Mount目錄名為javawar1_javawar1_vol

透過docker dashboard看Volumes, 發現 docker run 與docker-compose確實都使用不同volumes,為了確認命名,我又多建了一個javawar2目錄,docker-compose.yml 把port改為81:8080,確定新增了一個javawar2_javawar1_vol

- 這裡再多做一個小實驗,驗證多個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

- 結論:
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之後,如下

docker exec -it 也可指定container_name,不用去找container id

### 啟動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寫出的時間已是台北時區

但是時間還是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的最新版本

(production應該指定版本比較恰當)
---
### 加入Nginx
新增另一個目錄web_war來實作增加nginx
架構的變化可參考如下

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,可見服務啟動

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就夠用了