微服務實戰 – docker-compose實現mysql+springboot+angular

KerryWu發表於2019-01-19

前言

這是一次完整的專案實踐,Angular頁面+Springboot介面+MySQL都通過Dockerfile打包成docker映象,通過docker-compose做統一編排。目的是實現整個專案產品的輕量級和靈活性,在將各個模組的映象都上傳公共映象倉庫後,任何人都可以通過 “docker-compose up -d” 一行命令,將整個專案的前端、後端、資料庫以及檔案伺服器等,執行在自己的伺服器上。
本專案是開發一個類似於segmentfault的文章共享社群,我的設想是當部署在個人伺服器上時就是個人的文章庫,部署在專案組的伺服器上就是專案內部的文章庫,部署在公司的伺服器上就是所有職工的文章共享社群。突出的特點就是,專案相關的所有應用和檔案資源都是靈活的,使用者可以傻瓜式地部署並使用,對宿主機沒有任何依賴。
目前一共有三個docker映象,考慮以後打在一個映象中,但目前只能通過docker-compose來編排這三個映象。

  1. MySQL映象:以MySQL為基礎,將專案所用到的資料庫、表結構以及一些基礎表的資料庫,通過SQL指令碼打包在映象中。使用者在啟動映象後就自動建立了專案所有的資料庫、表和基礎表資料。
  2. SpringBoot映象:後臺介面通過SpringBoot開發,開發完成後直接可以打成映象,由於其內建tomcat,可以直接執行,資料庫指向啟動好的MySQL容器中的資料庫。
  3. Nginx(Angular)映象:Nginx映象中打包了Angular專案的dist目錄資源,以及default.conf檔案。主要的作用有:部署Angular專案頁面;掛載宿主機目錄作為檔案伺服器;以及反向代理SpringBoot介面,解決跨域問題等等。

最後三個docker容器的編排通過docker-compose來實現,三個容器之間的相互訪問都通過容器內部的別名,避免了宿主機遷移時ip無法對應的問題。為了方便開發,順便配了個自動部署。

MySQL映象

初始化指令碼

在專案完成後,需要生成專案所需資料庫、表結構以及基礎表資料的指令碼,保證在執行該docker容器中,啟動MySQL資料庫時,自動構建資料庫和表結構,並初始化基礎表資料。
Navicat for MySQL的客戶端支援匯出資料庫的表結構和表資料的SQL指令碼。
如果沒有安裝Navicat,可以在連線上容器中開發用的MySQL資料庫,通過mysqldump 命令匯出資料庫表結構和資料的SQL指令碼。下文中就是將資料庫的SQL指令碼匯出到宿主機的/bees/sql 目錄:

docker exec -it  mysql mysqldump -uroot -pPASSWORD 資料庫名稱 > /bees/sql/資料庫名稱.sql

以上只是匯出 表結構和表資料的指令碼,還要在SQL指令碼最上方加上 生成資料庫的SQL:

drop database if exists 資料庫名稱;
create database 資料庫名稱;
use 資料庫名稱;

通過以上兩個步驟,資料庫、表結構和表資料三者的初始化SQL指令碼就生成好了。

Dockerfile構建映象

我們生成的SQL指令碼叫 bees.sql,在MySQL官方映象中提供了容器啟動時自動執行/docker-entrypoint-initdb.d資料夾下的指令碼的功能(包括shell指令碼和sql指令碼),我們在後續生成映象的時候,將上述生成的SQL指令碼COPY到MySQL的/docker-entrypoint-initdb.d目錄下就可以了。
現在我們寫Dockerfile,很簡單:

FROM mysql

MAINTAINER kerry(kerry.wu@definesys.com)

COPY bees.sql /docker-entrypoint-initdb.d

將 bees.sql 和 Dockerfile 兩個檔案放在同一目錄,執行構建映象的命令就可以了:

docker build -t bees-mysql .

現在通過 docker images,就能看到本地的映象庫中就已經新建了一個 bees-mysql的映象啦。

SpringBoot映象

springboot構建映象的方式很多,有通過程式碼生成映象的,也有通過jar包生成映象的。我不想對程式碼有任何汙染,就選擇後者,通過生成的jar包構建映象。
建立一個目錄,上傳已經準備好的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放在同一目錄執行命令開始構建映象,同樣在本地映象庫中就生成了bees-springboot的映象:

docker build -t bees-springboot .

Nginx(Angular)映象

Nginx的配置

該映象主要在於nginx上conf.d/default.conf檔案的配置,主要實現三個需求:

1、Angualr部署
Angular的部署很簡單,只要將Angular專案通過 ng build –prod 命令生成dist目錄,將dist目錄作為靜態資原始檔放在伺服器上訪問就行。我們這裡就把dist目錄打包在nginx容器中,在default.conf上配置訪問。
2、檔案伺服器
專案為文章共享社群,少不了的就是一個儲存文章的檔案伺服器,包括儲存一些圖片之類的靜態資源。需要在容器中建立一個檔案目錄,通過default.conf上的配置將該目錄代理出來,可以直接訪問目錄中的檔案。
當然為了不丟失,這些檔案最好是儲存在宿主機上,在啟動容器時可以將宿主機本地的目錄掛載到容器中的檔案目錄。
3、介面跨域問題
在前後端分離開發的專案中,“跨域問題”是較為常見的,SpringBoot的容器和Angular所在的容器不在同一個ip和埠,我們同樣可以在default.conf上配置反向代理,將後臺介面代理成同一個ip和埠的地址。

話不多說,結合上面三個問題,我們最終的default.conf為:

server {
    listen       80;

    server_name  localhost;

    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;
    }
}
  1. location / :代理的是Angular專案,dist目錄內通過Dockerfile
    COPY在容器內的/usr/share/nginx/html目錄;
  2. location /file :代理/home/file 目錄,作為檔案伺服器;
  3. location /api/ :是為了解決跨域而做的反向代理,為了脫離宿主機的限制,介面所在容器的ip通過別名beesSpringboot來代替。別名的設定是在docker-compose.yml中設定的,後續再講。

Dockerfile構建映象

同樣建立一個目錄,包含Angualr的dist目錄、Dockerfile和nginx的default.conf檔案,目錄結構如下:

[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;"]

以上,通過下列命令,構建bees-nginx-angular的映象完成:

docker build -t bees-nginx-angular .  

docker-compose容器服務編排

上述,我們已經構建了三個映象,相對應的至少要啟動三個容器來完成專案的執行。那要執行三個docker run?太麻煩了,而且這三個容器之間還需要相互通訊,如果只使用docker來做的話,不光啟動容器的命令會很長,而且為了容器之間的通訊,docker –link 都會十分複雜,這裡我們需要一個服務編排。docker的編排名氣最大的當然是kubernetes,但我的初衷是讓這個專案輕量級,不太希望使用者安裝偏重量級的kubernetes才能執行,而我暫時又沒能解決將三個映象構建成一個映象的技術問題,就選擇了適中的一個產品–docker-compse。
安裝docker-compose很簡單,這裡就不贅言了。安裝完之後,隨便找個目錄,寫一個docker-compose.yml檔案,然後在該檔案所在地方執行一行命令就能將三個容器啟動了:

#啟動
docker-compose up -d
#關閉
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:容器的埠和宿主機的埠的對映
services:文中三個service,在各自容器啟動後就會自動生成別名,例如:在springboot中訪問資料庫,只需要通過“beesMysql:3306”就能訪問。
depends_on:會設定被依賴的容器啟動之後,才會啟動自己。例如:mysql資料庫容器啟動後,再啟動springboot介面的容器。
volumes:掛載卷,一些需要長久儲存的檔案,可通過宿主機中的目錄,掛載到容器中,否則容器重啟後會丟失。例如:資料庫的資料檔案;nginx的配置檔案和檔案伺服器目錄。

其他

自動部署

為了提高開發效率,簡單寫了一個自動部署的指令碼,直接貼指令碼了:

#!/bin/bash

v_springboot_jar=`find /bees/devops/upload/ -name "*.jar"`
echo "找到jar:"$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 "關閉容器"

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

遇到的坑

一開始在docker-compose.yml檔案中寫services時,每個service不是駝峰式命名,而是下劃線連線,例如:bees_springboot、bees_mysql、bees_nginx_angular 。
在springboot中訪問資料庫的別名可以,但是在nginx中,反向代理springboot介面地址時死活代理不了 bees_springboot的別名。能在bees_nginx_angular的容器中ping通bees_springboot,但是代理不了bees_springboot地址的介面,通過curl -v 檢視原因,是丟失了host。
最後發現,nginx預設request的header中包含“_”下劃線時,會自動忽略掉。我因此把docker-compose.yml中service名稱,從下劃線命名都改成了駝峰式。
當然也可以通過在nginx裡的nginx.conf配置檔案中的http部分中新增如下配置解決:

underscores_in_headers on;

相關文章