本篇原創發布於 Flex 的個人博客:點擊跳轉
前言
在沒有 docker 前,項目轉測試是比較麻煩的一件事。首先會化較長的時間搭建測試環境,然后在測試過程中又經常出現測試說是 bug,開發說無法復現的情況,導致撕逼。
本篇記錄了如何將一個 java 應用部署到 docker 中。主要講述了以下幾個部分:
- docker 部署 mysql
- docker 部署 activemq
- docker 部署 elastricsearch 集群
- docker 部署 java 應用
- docker 部署 nginx 作為靜態服務器,及代理服務器
項目架構如下:
本系統中有三個主要模塊 OMS,DAS 和一個 Eureka 注冊中心。其中 OMS 和 DAS 使用有 activemq 消息隊列,來進行大量數據的交互然后各自使用一個 mysql 數據庫存儲主要的業務數據。使用 elastricsearch 存儲超大量的數據。
傳統軟件部署和 docker 部署
本項目在 windows 部署時是將其作為三個部分來進行安裝的--ENV 環境包(保護 mysql,es 等),OMS 產品包,DAS 產品包。所以最初我的設想是一個容器中裝 ENV 環境包所需的所有軟件,一個容器裝 DAS,一個容器裝 OMS。然后再實踐的過程中越來越感覺不對勁,環境配置比較復雜,而且也有種把容器當虛擬機用的感覺,一點沒有簡化的感覺。
遂停下了操作,開始學習一波 docker 到底是怎么用的。用租房子來做比喻:
- 傳統軟件部署方式相當於租到一個零家具,零裝修的房子。我們想要住進去首先必須買齊必要的家具,然后想要住的舒心呢,還得花功夫裝飾裝飾,讓房子好看點。這樣就會對這個具體的房子產生較強的依賴,很難遷移到另一個房子中(想想那么多的家具,家電,雜物。。)。如果全部放棄重新換一個房子代價又太大了。
- 使用 docker 相當於租到一個全家電,精裝修的房子。我們只需帶上自己的個人物品即可開始入住。想要換一個房子也是輕而易舉,帶上自己的東西麻溜的就換了。
使用 docker 推薦操作是一個進程放到一個容器中,做到更好的隔離性,同時也更容易進行管理。下面來使用容器技術部署我們應用。還是分為三部分,但是每個進程使用一個容器,做到 0 配置啟動容器。
實戰
在此默認已經會安裝 docker,且了解基本操作。如不了解的先看這兩篇:安裝,基本使用
部署 ENV 環境包
環境包中諸如 elastricsearch,mysql 這樣的數據存儲工具,需要滿足如下兩個要求:
- 保留數據,不論容器如何創建、銷毀,數據不能丟。
- 需要使用個性化的配置文件,每次啟動根據該配置文件來啟動。
部署 mysql
使用容器部署 mysql 過程如下:
- 首先從 docker.hub 中根據各自的需求 pull 對應的 mysql 鏡像
docker pull mysql:5.7.24
- 啟動鏡像
由於 mysql 是用來存數據的,數據無論什么情況都不能丟失,所以數據存在容器外部,通過映射操作,映射到容器內部,參數如下:
# 將宿主機的路徑,映射到容器內部。這個路徑既可以是文件夾,也可以是文件
-v hostPath:containerPath
顯然我們通過這個桉樹將外部的數據文件夾,配置文件映射到容器中。最后啟動這個容器的命令如下:
# 假設在宿主機中數據存放路徑為/opt/mysql/data,配置文件路徑為:/opt/mysql/my.cnf
docker run --name=mysql -itd -p 3308:3306 -v /etc/localtime:/etc/localtime -v /etc/timezone:/etc/timezone -v /opt/mysql/data:/var/lib/mysql -v /opt/mysql/my.cnf:/etc/mysql/my.cnf -e MYSQL_ROOT_PASSWORD=123456 mysql:5.7.25
注意:如果 mysql 版本為 8.x,還需要映射容器中的/var/lib/mysql-files 目錄,否則啟動會報錯
下面介紹具體參數含義:
- -it 標准輸入輸出有關
- -d 后台啟動
- -v 文件映射
- -e 設置環境變量到容器中
可能你們會問為什么要映射/etc/timezone
和/etc/timezone
,這是為了讓容器的時間和時區與宿主機保持一致。默認情況下容器為 UTC 標准時間。/etc/timezone
讓容器時間,時區和宿主機一致。但是如果不映射/etc/timezone
java 應用中的時區還是錯的,雖然使用date -R
命令查看時間和時區都正常。
部署 elastricsearch,activeMQ 容器
es 和 activeMQ 都依賴 java 的運行環境,所以有兩種部署方式:
- 直接拉取 es,activeMQ 對應鏡像,通過路徑映射部署容器啟動
- 在一個 java 鏡像中運行 es 和 activeMQ
這里以第二種方式為例進行說明。
創建 java 鏡像
這里不從 docker hub 中拉取鏡像,通過 dockerfile 來制作一個自定義的鏡像。由於只需要一個 java 運行環境,所以只要將一個 jre 運行環境加入到一個基礎 linux 鏡像中即可(這里選擇 ubuntu)。制作過程如下:
首先創建一個文件夾dockerFileTest
存放依賴和 dockerfile 文件。
然后將下載加壓后的 jre 運行環境放到dockerFileTest/jre
目錄下。
接着在 dockerFileTest 目錄中創建 Dockerfile 文件,內容如下:
#說明基礎鏡像,默認:latest
FROM ubuntu
#將當前路徑下的jre文件夾復制到新鏡像下的/opt/jre路徑
COPY jre /opt/jre
#設置環境變量
ENV JAVA_HOME=/opt/jre CLASSPATH=/opt/jre/lib PATH=$PATH:/opt/jre/bin
最后通過``命令生成新鏡像 jre:v1
創建 elastricsearch 容器
下載好 es,假設存放在/root/es1 中,通過以下命令創建一個 es 容器:
docker run --name=es1 -itd -p 9200:9200 -p 9300:9300 -v /etc/localtime:/etc/localtime -v /etc/timezone:/etc/timezone -v /root/es1:/opt/es -w /opt/es jre:v1 ./bin/elasticsearch -Des.insecure.allow.root=true
參數含義如下:
- -w containerPath 設置容器工作目錄為 containerPath
上面的命令以 es1 為容器名,映射 9200,9300 到宿主機端口,以./bin/'elasticearch -Des.insecure.allow.root=true
創建 es 容器。最后加的參數為了讓 es 能夠以 root 在容器中啟動。
創建 es 集群
將之前的 es1 復制一份命名為 es2 作為節點 2。要讓兩個 es 節點構成 es 集群,需要讓節點間能夠進行通信,這里使用--link
參數來讓 es2 能夠連上 es1 構成集群。--link
用法如下:
--link containerName[:alias]
es2 啟動命令如下:
docker run --name=es2 -itd -p 9201:9200 -p 9301:9300 --link es1 -v /etc/localtime:/etc/localtime -v /etc/timezone:/etc/timezone -v /root/es2:/opt/es -w /opt/es jre:v1 ./bin/elasticsearch -Des.insecure.allow.root=true
然后就能在 es2 中通過 es1 的容器名訪問到 es1(實際是在 es2 的 host 中增加了一條記錄,將 es1 指向 es1 的 IP,該 IP 是 docker 的虛擬網卡分配的 IP)。
但是使用--link 有一些局限,通過該參數聯通的容器必須存在。因此該參數只能用在 B 依賴 A 的情況,如果同時 A 也依賴 B(也就是 A,B 要能夠相互訪問到),這種情況下就不能通過 link 來實現了,原因大家應該能夠想到。。。
部署 activeMQ
在容器中啟動 activeMQ 與啟動 es 稍有不同。activeMQ 默認是后台啟動的,啟動完成后啟動程序就會退出,因此如果直接以./bin/activemq start
(假設當前目錄在 activemq 中),啟動容器會發現在 activemq 啟動成功后容器就停止運行了。會出現這種情況是因為容器中啟動的第一個進程結束后,容器就會被docker關閉掉
。所以呢我們只需讓第一個進程不結束就行了,因此需要我們編寫一個啟動腳本來啟動 activemq 並監測運行情況,一旦 activemq 進程掛掉,就結束啟動腳本,否則一直運行。啟動腳本代碼如下:
#!/bin/bash
#使用sh腳本啟動activemq,然后定時判斷服務是否被關閉,關閉后退出腳本,否則一致循環。
#為避免docker容器在active自帶的啟動腳本運行結束后就關閉容器了。
#獲取啟動pid
out=`./bin/activemq start`
echo "$out"
pid=`echo $out | grep -Eo "pid '[0-9]+'" | grep -Eo "[0-9]+"`
echo "當前mq進程pid為:${pid}"
if [ ${#pid} = 0 ]; then
echo "啟動失敗"
exit 0
fi
while true; do
num=`ps -e | grep -cwE "$pid"`
if [ $num = 0 ]; then
echo "進程異常關閉"
exit 0
fi
sleep 1
done
然后以該腳本作為啟動腳本來啟動容器即可。啟動命令如下:(假設 activemq 目錄為/opt/activemq,啟動腳本路徑為/opt/activemq/start.sh)
docker run --name=activemq -itd -p 8161:8161 -p 61616:61616 -v /etc/localtime:/etc/localtime -v /etc/timezone:/etc/timezone -v /root/activemq:/opt/activemq -w /opt/activemq jre:v1 /bin/bash ./start.sh
部署 java 環境包
還是使用之前制作的 jar 鏡像來啟動 java 應用,這里以部署 jar 包為例,如果部署 war 包則需要在 tomcat 鏡像上部署。特別注意下容器的時間和時區設置,否則 java 程序中無法獲取到正確的時間和時區。這里通過映射宿主機的 localtime 和 timezone 文件來讓容器時間和時區與宿主機相同。啟動命令如下:
# 啟動oms
docker run -itd --name=oms -p 8082:9090 -v /etc/localtime:/etc/localtime -v /etc/timezone:/etc/timezone -v /root/oms.jar:/opt/oms.jar -w /opt --link es1 --link activemq --link oms-mysql --link eureka-server jre:v1 java -jar oms.jar
# 啟動das
docker run -itd --name=das -p 8083:9099 -v /etc/localtime:/etc/localtime -v /etc/timezone:/etc/timezone -v /root/das.jar:/opt/oms -w /opt --link es1 --link activemq --link oms-mysql --link eureka-server jre:v1 java -jar das.jar
本篇只是記錄了如何使用一容器一進程
的方式來部署 java 應用.
PS:不推薦這么直接手擼命令,建議使用 docker-compose
本篇原創發布於 Flex 的個人博客:點擊跳轉