---
# System prepended metadata

title: 包裝 Java War file with Tomcat to Docker Image
tags: [Tag(Tomcat), Tag(docker)]

---

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