在今年年初的時候,完成了本身的個Fame博客系統的實現,當時也作了一篇博文Spring-boot+Vue = Fame 寫blog的一次小結做爲記錄和介紹。從完成實現到如今,也斷斷續續的根據實際的使用狀況進行更新。javascript
只不過每次上線部署的時候都以爲有些麻煩,由於個人服務器內存過小,每次即便只更新了前臺部分(fame-front)的代碼,在執行npm build
的時候都還必須把個人後端服務(fame-server)的進程關掉,否則會形成服務器卡死(慘啊)。html
並且這個項目是先後端分離的,博客前臺頁面還爲了SEO用了Nuxt
框架,假如是第一次部署或者要服務器遷移的話,麻煩的要死啊,部署一次的話要如下步驟前端
npm install
,npm run build
等npm install
,npm run start
等若是可以順利的完成這七個步驟算是幸運兒了,假如中間哪一個步驟報錯出了問題,可能還要回頭查找哪一個步驟出了問題,而後又從新部署。vue
在這些需求面前,Docker就是解決這些問題的大殺器。不管是其虛擬化技術隔離各個容器使其資源互不影響,仍是一致的運行環境,以及docker-compose的一鍵部署,都完美的解決了上述問題。java
項目地址:Famenode
Docker和Docker-compose的功能和使用能夠看線上的一箇中文文檔Docker — 從入門到實踐mysql
下面是Centos7安裝和配置Docker以及Docker-compose的shell腳本,其餘操做系統能夠參考修改來安裝。其中Docker版本爲docker-ce
,Docker-compose版本爲1.22.0
linux
#!/bin/sh
### 更新 ###
yum -y update
### 安裝docker ###
# 安裝一些必要的系統工具
sudo yum install -y yum-utils device-mapper-persistent-data lvm2
# 添加軟件源信息
sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
# 更新 yum 緩存
sudo yum makecache fast
# 安裝 Docker-ce
sudo yum -y install docker-ce
# 啓動docker並設置爲開機啓動(centos7)
systemctl start docker.service
systemctl enable docker.service
# 替換docker爲國內源
echo '{"registry-mirrors": ["https://registry.docker-cn.com"],"live-restore": true}' > /etc/docker/daemon.json
systemctl restart docker
# 安裝dokcer-compose
sudo curl -L https://github.com/docker/compose/releases/download/1.22.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
# 安裝命令補全工具
yum -y install bash-completion
curl -L https://raw.githubusercontent.com/docker/compose/$(docker-compose version --short)/contrib/completion/bash/docker-compose > /etc/bash_completion.d/docker-compose
### 安裝docker結束 ###
複製代碼
先看一下改造後的項目的結構webpack
├─Fame
│ │ .env // docker-compose環境參數配置文件
│ │ docker-compose.yml // docker-compose文件
│ ├─fame-docker
│ │ │ fame-front-Dockerfile // fame-front的Dockerfile文件
│ │ │ fame-server-Dockerfile // fame-server的Dockerfile文件
│ │ │
│ │ ├─fame-admin
│ │ │ fame-admin-Dockerfile // fame-admin的Dockerfile文件
│ │ │ nginx.conf // fame-admin的nginx服務器配置文件
│ │ │
│ │ ├─fame-mysql
│ │ │ fame-mysql-Dockerfile // mysql的Dockerfile文件
│ │ │ mysqld.cnf // mysql的配置文件mysqld.cnf
│ │ │
│ │ └─fame-nginx
│ │ nginx-Dockerfile // 整個項目的nginx服務器的Dockerfile文件
│ │ nginx.conf // 整個項目的nginx的配置文件
│ │
│ ├─fame-admin // 博客管理後臺,基於Vue+elementui
│ ├─fame-front // 博客前端,基於Nuxt
│ └─fame-server // 博客服務端,基於spring-boot
複製代碼
爲了避免破壞原有項目的結構,不管前端仍是後端的docker的相關配置文件所有提取出來,單獨放在了fame-docker
文件夾中。ios
docker-compose.yml
放在項目根目錄下,直接在根目錄運行命令:docker-compose up -d
[root@localhost Fame]# docker-compose up -d
Starting fame-front ...
Starting fame-admin ...
Starting fame-front ... done
Starting fame-admin ... done
Starting fame-nginx ... done
複製代碼
就啓動項目了,不再用重複繁瑣的步驟!
docker-compose.yaml
文件version: '3'
services:
fame-nginx:
container_name: fame-nginx
build:
context: ./
dockerfile: ./fame-docker/fame-nginx/nginx-Dockerfile
ports:
- "80:80"
volumes:
- ./logs/nginx:/var/log/nginx
depends_on:
- fame-server
- fame-admin
- fame-front
fame-mysql:
container_name: fame-mysql
build:
context: ./
dockerfile: ./fame-docker/fame-mysql/fame-mysql-Dockerfile
environment:
MYSQL_DATABASE: fame
MYSQL_ROOT_PASSWORD: root
MYSQL_ROOT_HOST: '%'
TZ: Asia/Shanghai
expose:
- "3306"
volumes:
- ./mysql/mysql_data:/var/lib/mysql
restart: always
fame-server:
container_name: fame-server
restart: always
build:
context: ./
dockerfile: ./fame-docker/fame-server-Dockerfile
working_dir: /app
volumes:
- ./fame-server:/app
- ~/.m2:/root/.m2
- ./logs/fame:/app/log
expose:
- "9090"
command: mvn clean spring-boot:run -Dspring-boot.run.profiles=docker -Dmaven.test.skip=true
depends_on:
- fame-mysql
fame-admin:
container_name: fame-admin
build:
context: ./
dockerfile: ./fame-docker/fame-admin/fame-admin-Dockerfile
args:
BASE_URL: ${BASE_URL}
expose:
- "3001"
fame-front:
container_name: fame-front
build:
context: ./
dockerfile: ./fame-docker/fame-front-Dockerfile
environment:
BASE_URL: ${BASE_URL}
PROXY_HOST: ${PROXY_HOST}
PROXY_PORT: ${PROXY_PORT}
expose:
- "3000"
複製代碼
docker-compose.yml
的結構和剛纔目錄結構大致相似,也是分如下幾個部分
這個docker-compose.yml
中有幾個要點
fame-mysql
和fame-server
的restart
要設置爲always
,由於目前Docker-compose是沒有一個方案能夠解決容器啓動的前後的問題的。即便設置了depends_on
,那也只是控制容器開始啓動的時間,不能控制容器啓動完成的時間,因此讓fame-mysql
和fame-server
這兩個容器設置restart
,防止spring-boot在mysql啓動完成以前啓動而報錯啓動失敗fame-server
,fame-mysql
,fame-nginx
這三個容器都設置了volumes
,把容器裏的logs日誌文件掛載到宿主機的項目目錄裏,方便隨時看日誌文件fame-mysql
容器的mysql存儲文件也設置了volumes
掛載在項目目錄裏(./mysql/mysql_data:/var/lib/mysql
),這個建議你們能夠根據實際的狀況設置到宿主機的其餘目錄裏,否則不當心刪除項目的話那麼容器裏的數據庫數據也都沒了幾個鏡像的Dockerfile大部分都比較簡單,這部分就不所有詳細介紹了,能夠直接去我項目中瞭解。
爲了可以讓spring-boot可以在開發環境和Docker環境下快速切換,須要將spring-boot的配置文件進行修改
└─fame-server
...
│ └─resources
│ │ application-dev.properties
│ │ application-docker.properties
│ │ application.properties
複製代碼
在原有的application.properties
基礎上增長application-dev.properties
和application-docker.properties
配置文件,把application.properties
裏的數據庫日誌等信息分別放到application-dev.properties
和application-docker.properties
這兩個文件中,實現開發環境和Docker環境的快速切換。
# application.properties文件
#端口號
server.port=9090
#mybatis
mybatis.type-aliases-package=com.zbw.fame.Model
#mapper
mapper.mappers=com.zbw.fame.util.MyMapper
mapper.not-empty=false
mapper.identity=MYSQL
#mail
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.smtp.starttls.required=true
#默認properties
spring.profiles.active=dev
複製代碼
# application-docker.properties文件
#datasource
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://fame-mysql:3306/fame?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=root
#log
logging.level.root=INFO
logging.level.org.springframework.web=INFO
logging.file=log/fame.log
複製代碼
application-dev.properties
的內容和application-docker.properties
文件相似,只是根據本身開發環境的狀況修改mysql和log配置。
baseUrl
地址在fame-admin
和fame-front
中用了axios
插件,用於發起和獲取fame-server
服務器的請求。在axios
要配置服務器url地址baseUrl
,那麼一般開發環境和Docker環境以及生產環境的url可能都不同,每次都去修改有點麻煩。(雖然只須要配置兩處,可是代碼潔癖不容許我硬編碼這個配置)。
先修改fame-admin(Vue)
使其兼容手動部署模式和Docker模式
fame-admin
是基於Vue CLI 3
搭建的,相對於cli 2.0官方把webpack的一些配置文件都封裝起來了,因此沒有config和build文件夾。不過對應的官網也給了一些設置更加方便的配置參數。
在官方文檔中提到:
只有以
VUE_APP_
開頭的變量會被webpack.DefinePlugin
靜態嵌入到客戶端側的包中。你能夠在應用的代碼中這樣訪問它們:console.log(process.env.VUE_APP_SECRET) 複製代碼
在構建過程當中,
process.env.VUE_APP_SECRET
將會被相應的值所取代。在VUE_APP_SECRET=secret
的狀況下,它會被替換爲"sercet"
。
利用這個特性來設置環境變量來動態的設置Docker模式和手動部署模式的baseUrl
的值
在fame-admin
目錄下建立文件server-config.js
,編寫如下內容
const isProd = process.env.NODE_ENV === 'production'
const localhost = 'http://127.0.0.1:9090/'
const baseUrl = process.env.VUE_APP_API_URL || localhost
const api = isProd ? baseUrl : localhost
export default {
isProd,
api
}
複製代碼
那麼只要在環境變量中有VUE_APP_API_URL
的值,且NODE_ENV === 'production'
,baseUrl就等於VUE_APP_API_URL
的值,不然就是localhost
的值。
接着在axios
配置文件中引用該文件設置
// fame-admin/src/plugins/http.js
...
import serverConfig from '../../server-config'
const Axios = axios.create({
baseURL: serverConfig.api + 'api/',
...
})
...
複製代碼
如今只要將docker的環境變量設置一個VUE_APP_API_URL
的值就好了,只要在對應的Dockerfile中增長一個步驟就能夠了。
ENV VUE_APP_API_URL http://xx.xxx.xxx.xxx
複製代碼
再修改fame-front(Nuxt)
使其兼容手動部署模式和Docker模式
一樣的,對於用Nuxt
搭建fame-front
博客前臺修改也是相似的思路。
在Nuxt的官方文檔中寫到:
Nuxt.js 讓你能夠配置在客戶端和服務端共享的環境變量。
例如 (
nuxt.config.js
):module.exports = { env: { baseUrl: process.env.BASE_URL || 'http://localhost:3000' } } 複製代碼
以上配置咱們建立了一個
baseUrl
環境變量,若是應用設定了BASE_URL
環境變量,那麼baseUrl
的值等於BASE_URL
的值,不然其值爲http://localhost:3000
。
因此咱們只要和官方文檔說的同樣,在nuxt.config.js
文件中增長代碼就能夠了
module.exports = {
env: {
baseUrl: process.env.BASE_URL || 'http://localhost:3000'
}
}
複製代碼
接着在server-config.js
文件和axios
的配置文件fame-front/plugins/http.js
以及對應的Dockerfile文件中編寫和上面fame-admin
部分同樣的代碼就能夠了
如今已經把baseUrl
的設置從代碼的硬編碼中解放出來了,但事實上咱們只是把這個參數的編碼從代碼從轉移到Dockerfile文件裏了,要是想要修改的話也要去這兩個文件裏查找而後修改,這樣也不方便。後面會解決這個問題把全部環境配置統一塊兒來。
先要說明一點,爲何博客前端要單獨去使用的Nuxt而不是和博客後臺同樣用Vue呢,由於博客前端有SEO的需求的,像Vue這樣的對搜索引擎很不友好。
因此Nuxt的頁面是服務器端渲染(SSR)的
fame-front
的頁面在渲染以前必須獲取到fame-server
服務器中的數據,可是每一個docker容器都是互相獨立的,其內部想要互相訪問只能經過容器名訪問。例如容器fame-front
想要訪問容器fame-server
,就設置baseURL = fame-server
(fame-server是服務器的容器的container_name)。
這樣設置以後打開瀏覽器輸入網址:xx.xxx.xxx.xx能夠成功訪問到頁面,可是隨便點擊一個連接,就會看到瀏覽器提示錯誤沒法訪問到地址http://fame-server/...
vendor.e2feb665ef91f298be86.js:2 GET http://fame-server/api/article/1 net::ERR_CONNECTION_REFUSED
複製代碼
這是必然的結果,在容器裏http://fame-server/就是服務器的地址,可是你本地的瀏覽器固然是不知道http://fame-server/是個什麼鬼地址,因此就瀏覽器就報出沒法訪問的錯誤。
什麼?但是剛纔不是說Nuxt是服務器渲染的頁面嗎,怎麼又讓本地瀏覽器報這個錯誤了。
原來是由於當經過瀏覽器連接直接訪問的時候,Nuxt的確是從後端渲染了頁面再傳過來,可是在頁面中點擊連接的時候是經過Vue-Router跳轉的,這時候不在Nuxt的控制範圍,而是和Vue同樣在瀏覽器渲染的,這時候就要從瀏覽器裏向服務端獲取數據來渲染,瀏覽器就會報錯。
這個問題開始的時候一直想要嘗試配置Docker容器的網絡模式來解決,但是都沒有解決。直到後面我看axios
文檔的時候才注意到axios
的代理功能,其本質是解決跨域的問題的,由於只要在axios
設置了代理,在服務端渲染的時候就會使用代理的地址,同時在瀏覽器訪問的時候會用baseUrl
的地址,這個特色完美解決個人問題啊。
在server-config.js
文件裏增長如下代碼(在nuxt.config.js
裏獲取環境變量裏的proxyHost
和proxyPort
)
...
const localProxy = {
host: '127.0.0.1',
port: 9090
}
const baseProxy = {
host: process.env.proxyHost || localProxy.host,
port: process.env.proxyPort || localProxy.port
}
exports.baseProxy = isProd ? baseProxy : localProxy
...
複製代碼
而後在axios
配置文件裏增長代碼
// fame-front/plugins/http.js
const Axios = axios.create({
proxy: serverConfig.baseProxy
...
})
...
複製代碼
就能夠完美的解決問題了。
在上文解決動態配置axios地址
的部分把baseUrl
的設置放在了Dockerfile中,如今就再把Dockerfile中的硬編碼提取出來,放到統一的配置文件中。
首先在docker-compose.yml
文件目錄下(即項目跟目錄)建立環境文件.env
並編寫一下內容
BASE_URL=http://xx.xxx.xxx.xxx
PROXY_HOST=fame-nginx
PROXY_PORT=80
複製代碼
這個是docker-compose
的env_file
參數,從文件中獲取環境變量,能夠爲單獨的文件路徑或列表,若是同目錄下有.env
文件則會默認讀取,也能夠本身在docker-compose
裏設置路徑。
已經在.env
設置了環境變量BASE_URL
的值,就能在docker-compose.yml
裏直接使用了。修改docker-compose.yml
的fame-front
部分:
fame-front:
...
environment:
BASE_URL: ${BASE_URL}
PROXY_HOST: ${PROXY_HOST}
PROXY_PORT: ${PROXY_PORT}
...
複製代碼
這樣在fame-front
的容器裏就有對應的BASE_URL
,PROXY_HOST
,PROXY_PORT
環境變量,Nuxt也可以成功獲取並設置。
不過對於fame-admin
容器來講就要稍微複雜一點點了。先來看一下fame-admin
容器的Dockerfile文件fame-admin-Dockerfile
# build stage
FROM node:10.10.0-alpine as build-stage
#中間一些操做省略...
RUN npm run build
# production stage
FROM nginx:1.15.3-alpine as production-stage
COPY ./fame-docker/fame-admin/nginx.conf /etc/nginx/conf.d/default.conf COPY --from=build-stage /app/dist /usr/share/nginx/html EXPOSE 80
CMD ["nginx", "-g", "daemon off;"] 複製代碼
這裏用了多階段構建容器,若是直接經過docker-compose
設置環境變量只會在後面一個階段生效,可是npm run build
是在第一個階段執行的,因此環境變量不能應用到Vue當中。爲了讓環境變量在第一階段就應用,必需要在構建的時候就把變量從docker-compose
傳到fame-admin-Dockerfile
中,而後在Dockerfile中的第一階段把這個環境變量應用到容器裏。下面修改docker-compose.yml
的fame-admin
部分:
fame-admin:
...
build:
context: ./
dockerfile: ./fame-docker/fame-admin/fame-admin-Dockerfile
args:
BASE_URL: ${BASE_URL} # 這裏把環境變量當作ARG傳給Dockerfile
...
複製代碼
而後在fame-admin-Dockerfile
的第一階段增長步驟
# build stage
FROM node:10.10.0-alpine as build-stage
ARG BASE_URL # 必須申明這個ARG才能從docker-compose裏獲取
ENV VUE_APP_API_URL $BASE_URL
# 如下省略...
複製代碼
這樣就能夠在構建階段一鏡像的時候就把環境變量傳入到階段一的鏡像裏,讓Vue裏的變量生效了。
如今網上不少複雜一點的項目即便用了docker-compose部署,也多少依賴shell腳原本操做,好比複製文件設置環境等,我以爲這樣會下降docker-compose的意義。若是都使用了shell腳本,那不如直接不用docker-compose而全用shell來構建和啓動鏡像。
因此在Docker化的過程當中雖然遇到一些坎坷,但堅持實現了只用docker-compose部署,之後上線和下線就及其方便了。也但願個人Docker化思路能夠給其餘項目作一些參考。
對比之前恐怖的步驟,如今Fame博客的上線和下線只須要兩行命令,真的十分的便捷。
docker-compose up
docker-compose down
複製代碼
源碼地址:doodle