一個網(wǎng)站的微服務(wù)架構(gòu)實戰(zhàn)docker和 docker-compose
作者:KerryWu
來源:SegmentFault 思否社區(qū)
前言
這是一次完整的項目實踐,Angular頁面+Springboot接口+MySQL都通過Dockerfile打包成docker鏡像,通過docker-compose做統(tǒng)一編排。目的是實現(xiàn)整個項目產(chǎn)品的輕量級和靈活性,在將各個模塊的鏡像都上傳公共鏡像倉庫后,任何人都可以通過 “docker-compose up -d” 一行命令,將整個項目的前端、后端、數(shù)據(jù)庫以及文件服務(wù)器等,運行在自己的服務(wù)器上。
本項目是開發(fā)一個類似于segmentfault的文章共享社區(qū),我的設(shè)想是當(dāng)部署在個人服務(wù)器上時就是個人的文章庫,部署在項目組的服務(wù)器上就是項目內(nèi)部的文章庫,部署在公司的服務(wù)器上就是所有職工的文章共享社區(qū)。突出的特點就是,項目相關(guān)的所有應(yīng)用和文件資源都是靈活的,用戶可以傻瓜式地部署并使用,對宿主機(jī)沒有任何依賴。
目前一共有三個docker鏡像,考慮以后打在一個鏡像中,但目前只能通過docker-compose來編排這三個鏡像。
MySQL鏡像:以MySQL為基礎(chǔ),將項目所用到的數(shù)據(jù)庫、表結(jié)構(gòu)以及一些基礎(chǔ)表的數(shù)據(jù)庫,通過SQL腳本打包在鏡像中。用戶在啟動鏡像后就自動創(chuàng)建了項目所有的數(shù)據(jù)庫、表和基礎(chǔ)表數(shù)據(jù)。 SpringBoot鏡像:后臺接口通過SpringBoot開發(fā),開發(fā)完成后直接可以打成鏡像,由于其內(nèi)置tomcat,可以直接運行,數(shù)據(jù)庫指向啟動好的MySQL容器中的數(shù)據(jù)庫。 Nginx(Angular)鏡像:Nginx鏡像中打包了Angular項目的dist目錄資源,以及default.conf文件。主要的作用有:部署Angular項目頁面;掛載宿主機(jī)目錄作為文件服務(wù)器;以及反向代理SpringBoot接口,解決跨域問題等等。
最后三個docker容器的編排通過docker-compose來實現(xiàn),三個容器之間的相互訪問都通過容器內(nèi)部的別名,避免了宿主機(jī)遷移時ip無法對應(yīng)的問題。為了方便開發(fā),順便配了個自動部署。
MySQL鏡像
初始化腳本
在項目完成后,需要生成項目所需數(shù)據(jù)庫、表結(jié)構(gòu)以及基礎(chǔ)表數(shù)據(jù)的腳本,保證在運行該docker容器中,啟動MySQL數(shù)據(jù)庫時,自動構(gòu)建數(shù)據(jù)庫和表結(jié)構(gòu),并初始化基礎(chǔ)表數(shù)據(jù)。
Navicat for MySQL的客戶端支持導(dǎo)出數(shù)據(jù)庫的表結(jié)構(gòu)和表數(shù)據(jù)的SQL腳本。如果沒有安裝Navicat,可以在連接上容器中開發(fā)用的MySQL數(shù)據(jù)庫,通過mysqldump 命令導(dǎo)出數(shù)據(jù)庫表結(jié)構(gòu)和數(shù)據(jù)的SQL腳本。下文中就是將數(shù)據(jù)庫的SQL腳本導(dǎo)出到宿主機(jī)的/bees/sql 目錄:
docker?exec?-it??mysql?mysqldump?-uroot?-pPASSWORD?數(shù)據(jù)庫名稱?>?/bees/sql/數(shù)據(jù)庫名稱.sql
以上只是導(dǎo)出 表結(jié)構(gòu)和表數(shù)據(jù)的腳本,還要在SQL腳本最上方加上 生成數(shù)據(jù)庫的SQL:
drop?database?if?exists?數(shù)據(jù)庫名稱;
create?database?數(shù)據(jù)庫名稱;
use?數(shù)據(jù)庫名稱;
通過以上兩個步驟,數(shù)據(jù)庫、表結(jié)構(gòu)和表數(shù)據(jù)三者的初始化SQL腳本就生成好了。
Dockerfile構(gòu)建鏡像
我們生成的SQL腳本叫 bees.sql,在MySQL官方鏡像中提供了容器啟動時自動執(zhí)行/docker-entrypoint-initdb.d文件夾下的腳本的功能(包括shell腳本和sql腳本),我們在后續(xù)生成鏡像的時候,將上述生成的SQL腳本COPY到MySQL的/docker-entrypoint-initdb.d目錄下就可以了。
現(xiàn)在我們寫Dockerfile,很簡單:
FROM?mysql
MAINTAINER?kerry([email protected])
COPY?bees.sql?/docker-entrypoint-initdb.d
將 bees.sql 和 Dockerfile 兩個文件放在同一目錄,執(zhí)行構(gòu)建鏡像的命令就可以了:
docker?build?-t?bees-mysql?.
現(xiàn)在通過 docker images,就能看到本地的鏡像庫中就已經(jīng)新建了一個 bees-mysql的鏡像啦。
SpringBoot鏡像
springboot構(gòu)建鏡像的方式很多,有通過代碼生成鏡像的,也有通過jar包生成鏡像的。我不想對代碼有任何污染,就選擇后者,通過生成的jar包構(gòu)建鏡像。
創(chuàng)建一個目錄,上傳已經(jīng)準(zhǔn)備好的springboot的jar包,這里名為bees-0.0.1-SNAPSHOT.jar,然后同樣編寫Dockerfile文件:
FROM?java:8
VOLUME?/tmp
ADD?bees-0.0.1-SNAPSHOT.jar?/bees-springboot.jar
EXPOSE?8010
ENTRYPOINT?["java","-Djava.security.egd=file:/dev/./urandom","-jar","-Denv=DEV","/bees-springboot.jar"]
將bees-0.0.1-SNAPSHOT.jar和Dockerfile放在同一目錄執(zhí)行命令開始構(gòu)建鏡像,同樣在本地鏡像庫中就生成了bees-springboot的鏡像:
docker?build?-t?bees-springboot?.
Nginx(Angular)鏡像
Nginx的配置
該鏡像主要在于nginx上conf.d/default.conf文件的配置,主要實現(xiàn)三個需求:
1、Angualr部署
Angular的部署很簡單,只要將Angular項目通過 ng build --prod 命令生成dist目錄,將dist目錄作為靜態(tài)資源文件放在服務(wù)器上訪問就行。我們這里就把dist目錄打包在nginx容器中,在default.conf上配置訪問。
2、文件服務(wù)器
項目為文章共享社區(qū),少不了的就是一個存儲文章的文件服務(wù)器,包括存儲一些圖片之類的靜態(tài)資源。需要在容器中創(chuàng)建一個文件目錄,通過default.conf上的配置將該目錄代理出來,可以直接訪問目錄中的文件。
當(dāng)然為了不丟失,這些文件最好是保存在宿主機(jī)上,在啟動容器時可以將宿主機(jī)本地的目錄掛載到容器中的文件目錄。
3、接口跨域問題
在前后端分離開發(fā)的項目中,“跨域問題”是較為常見的,SpringBoot的容器和Angular所在的容器不在同一個ip和端口,我們同樣可以在default.conf上配置反向代理,將后臺接口代理成同一個ip和端口的地址。
話不多說,結(jié)合上面三個問題,我們最終的default.conf為:
server?{
????listen???????80;
????server_name??localhost;
????
????gzip?on;
????gzip_min_length??1k;
????gzip_buffers?????4?16k;
????gzip_comp_level?3;
????gzip_types???????text/plain?application/x-javascript?application/javascript?text/css?application/xml?text/javascript?application/x-httpd-php?image/jpeg?image/gif?image/png;
????gzip_vary?on;
????location?/?{
????????root???/usr/share/nginx/html;
????????index??index.html?index.htm;
????????try_files?$uri?$uri/?/index.html;
????}
????
????location?/api/?{
????????proxy_pass?http://beesSpringboot:8010/;
????}
????location?/file?{
????????alias?/home/file;
????????index??index.html?index.htm;
????}
????error_page???500?502?503?504??/50x.html;
????location?=?/50x.html?{
????????root???/usr/share/nginx/html;
????}
}
location / :代理的是Angular項目,dist目錄內(nèi)通過Dockerfile
COPY在容器內(nèi)的/usr/share/nginx/html目錄;location /file :代理/home/file 目錄,作為文件服務(wù)器; location /api/ :是為了解決跨域而做的反向代理,為了脫離宿主機(jī)的限制,接口所在容器的ip通過別名beesSpringboot來代替。別名的設(shè)置是在docker-compose.yml中設(shè)置的,后續(xù)再講。 添加了gzip,針對前端較大資源文件下載速度的優(yōu)化
Dockerfile構(gòu)建鏡像
同樣創(chuàng)建一個目錄,包含Angualr的dist目錄、Dockerfile和nginx的default.conf文件,目錄結(jié)構(gòu)如下:
[root@Kerry?angular]#?tree
.
├──?dist
│???└──?Bees
│???????├──?0.cb202cb30edaa3c93602.js
│???????├──?1.3ac3c111a5945a7fdac6.js
│???????├──?2.99bfc194c4daea8390b3.js
│???????├──?3.50547336e0234937eb51.js
│???????├──?3rdpartylicenses.txt
│???????├──?4.53141e3db614f9aa6fe0.js
│???????├──?assets
│???????│???└──?images
│???????│???????├──?login_background.jpg
│???????│???????└──?logo.png
│???????├──?favicon.ico
│???????├──?index.html
│???????├──?login_background.7eaf4f9ce82855adb045.jpg
│???????├──?main.894e80999bf907c5627b.js
│???????├──?polyfills.6960d5ea49e64403a1af.js
│???????├──?runtime.37fed2633286b6e47576.js
│???????└──?styles.9e4729a9c6b60618a6c6.css
├──?Dockerfile
└──?nginx
????└──?default.conf
Dockerfile文件如下:
FROM?nginx
COPY?nginx/default.conf?/etc/nginx/conf.d/
RUN?rm?-rf?/usr/share/nginx/html/*
COPY?/dist/Bees?/usr/share/nginx/html
CMD?["nginx",?"-g",?"daemon?off;"]
以上,通過下列命令,構(gòu)建bees-nginx-angular的鏡像完成:
`docker?build?-t?bees-nginx-angular?.
docker-compose容器服務(wù)編排
上述,我們已經(jīng)構(gòu)建了三個鏡像,相對應(yīng)的至少要啟動三個容器來完成項目的運行。那要執(zhí)行三個docker run?太麻煩了,而且這三個容器之間還需要相互通信,如果只使用docker來做的話,不光啟動容器的命令會很長,而且為了容器之間的通信,docker --link 都會十分復(fù)雜,這里我們需要一個服務(wù)編排。docker的編排名氣最大的當(dāng)然是kubernetes,但我的初衷是讓這個項目輕量級,不太希望用戶安裝偏重量級的kubernetes才能運行,而我暫時又沒能解決將三個鏡像構(gòu)建成一個鏡像的技術(shù)問題,就選擇了適中的一個產(chǎn)品--docker-compse。
安裝docker-compose很簡單,這里就不贅言了。安裝完之后,隨便找個目錄,寫一個docker-compose.yml文件,然后在該文件所在地方執(zhí)行一行命令就能將三個容器啟動了:
#啟動
docker-compose?up?-d
#關(guān)閉
docker-compose?down
這里直接上我寫的docker-compose.yml文件
version:?"2"
services:
?beesMysql:
??restart:?always
??image:?bees-mysql
??ports:
???-?3306:3306
??volumes:
???-?/bees/docker_volume/mysql/conf:/etc/mysql/conf.d
???-?/bees/docker_volume/mysql/logs:/logs
???-?/bees/docker_volume/mysql/data:/var/lib/mysql
??environment:
???MYSQL_ROOT_PASSWORD:?kerry
?beesSpringboot:
??restart:?always
??image:?bees-springboot
??ports:
???-?8010:8010
??depends_on:
???-?beesMysql
?beesNginxAngular:
??restart:?always
??image:?bees-nginx-angular
??ports:
???-?8000:80
??depends_on:
???-?beesSpringboot
??volumes:
???-?/bees/docker_volume/nginx/nginx.conf:/etc/nginx/nginx.conf
???-?/bees/docker_volume/nginx/conf.d:/etc/nginx/conf.d
???-?/bees/docker_volume/nginx/file:/home/file
image:鏡像名稱
ports:容器的端口和宿主機(jī)的端口的映射
services:文中三個service,在各自容器啟動后就會自動生成別名,例如:在springboot中訪問數(shù)據(jù)庫,只需要通過“beesMysql:3306”就能訪問。
depends_on:會設(shè)置被依賴的容器啟動之后,才會啟動自己。例如:mysql數(shù)據(jù)庫容器啟動后,再啟動springboot接口的容器。
volumes:掛載卷,一些需要長久保存的文件,可通過宿主機(jī)中的目錄,掛載到容器中,否則容器重啟后會丟失。例如:數(shù)據(jù)庫的數(shù)據(jù)文件;nginx的配置文件和文件服務(wù)器目錄。
其他
自動部署
為了提高開發(fā)效率,簡單寫了一個自動部署的腳本,直接貼腳本了:
#!/bin/bash
v_springboot_jar=`find?/bees/devops/upload/?-name?"*.jar"`
echo?"找到j(luò)ar:"$v_springboot_jar
v_angular_zip=`find?/bees/devops/upload/?-name?"dist.zip"`
echo?"找到dist:"$v_angular_zip
cd?/bees/conf/
docker-compose?down
echo?"關(guān)閉容器"
docker?rmi?-f?$(docker?images?|??grep?"bees-springboot"??|?awk?'{print?$1}')
docker?rmi?-f?$(docker?images?|??grep?"bees-nginx-angular"??|?awk?'{print?$1}')
echo?"刪除鏡像"
cd?/bees/devops/dockerfiles/springboot/
rm?-f?*.jar
cp?$v_springboot_jar?./bees-0.0.1-SNAPSHOT.jar
docker?build?-t?bees-springboot?.
echo?"生成springboot鏡像"
cd?/bees/devops/dockerfiles/angular/
rm?-rf?dist/
cp?$v_angular_zip?./dist.zip
unzip?dist.zip
rm?-f?dist.zip
docker?build?-t?bees-nginx-angular?.
echo?"生成angular鏡像"
cd?/bees/conf/
docker-compose?up?-d
echo?"啟動容器"
docker?ps?|grep?bees
遇到的坑
一開始在docker-compose.yml文件中寫services時,每個service不是駝峰式命名,而是下劃線連接,例如:bees_springboot、bees_mysql、bees_nginx_angular 。
在springboot中訪問數(shù)據(jù)庫的別名可以,但是在nginx中,反向代理springboot接口地址時死活代理不了 bees_springboot的別名。能在bees_nginx_angular的容器中ping通bees_springboot,但是代理不了bees_springboot地址的接口,通過curl -v 查看原因,是丟失了host。
最后發(fā)現(xiàn),nginx默認(rèn)request的header中包含“_”下劃線時,會自動忽略掉。我因此把docker-compose.yml中service名稱,從下劃線命名都改成了駝峰式。
當(dāng)然也可以通過在nginx里的nginx.conf配置文件中的http部分中添加如下配置解決:
underscores_in_headers?on;點擊左下角閱讀原文,到?SegmentFault 思否社區(qū)?和文章作者展開更多互動和交流。 -?END -

