將你的前端應用打包成docker映象並部署到伺服器?僅需一個指令碼搞定

SH的全棧筆記發表於2019-04-23

1.前言

前段時間,自己搞了個阿里雲的伺服器。想自己在上面折騰,但是不想因為自己瞎折騰而汙染了現有的環境。畢竟,現在的阿里雲已經沒有免費的快照服務了。要想還原的話,最簡單的辦法就是重新裝系統。而一旦重灌,之前的搭建的所有環境就都白搭了。

再加上之前本身就想引入docker,所以就打算利用docker容器來部署這次的前端應用。

2.構建前端應用

在打包之前,首先需要一個可正常執行的前端應用。這個可以使用umi或者create-react-app來構建。

3.nginx的預設配置檔案

然後需要在專案中加上預設nginx配置檔案。

server {
    listen 80;
    server_name localhost;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
        try_files $uri $uri/ /index.html;
    }
}
複製程式碼

4.編寫本地構建指令碼

4.1. 移除上次的目錄和Dockerfile

#!/bin/bash

if [ -d "./dist" ]; then
    rm -rf ./dist
fi

if [ -f "./Dockerfile" ]; then
    rm -f ./Dockerfile
fi
複製程式碼

因為每次更改後dist中的內容肯定與之前不同,其實這一步顯得不是那麼必要。執行npm的打包命令也會自動清楚該目錄。

而清除Dockerfile則是為了防止更新了Dockerfile,而這次卻不能得到最新的配置。

4.2. 打包前端應用

執行前端的打包命令,生成靜態檔案目錄。

yarn build
複製程式碼

4.3. 生成Dockerfile

echo "FROM nginx:latest" >> ./Dockerfile
echo "COPY ./dist /usr/share/nginx/html/" >> ./Dockerfile
echo "COPY ./default.conf /etc/nginx/conf.d/" >> ./Dockerfile
echo "EXPOSE 80" >> ./Dockerfile
複製程式碼

FROM制定了該定製容器的基礎映象為nginx:latest;COPY命裡將打包好的靜態檔案目錄複製到容器內的/usr/share/nginx/html/目錄下,然後將nginx的配置寫入容器中對應的位置; EXPOSE則是設定對外暴露容器的80埠。

4.4. 生成並推送定製image

docker build -t detectivehlh/mine .
docker login -u detectivehlh -p ********
docker push detectivehlh/mine
複製程式碼

這裡是在開發本地,使用docker命令來打包,所以該指令碼對docker有強依賴。build命令表示打包docker應用的,-t選項則制定了docker映象的名字和tag,tag會預設為latest。

然後登入dockerHub,將定製好的映象推送到dockerHub中。detectivehlh就是dockerHub的使用者名稱,mine是image的名字。

4.5. 刪除tag為none的無用image

第一次構建不會生成tag為none的image,但是後面每次再次執行該命令就會出現這樣的情況。所以每次構建了一個新的image後,需要清除調不需要的image。

docker images | grep none | awk '{print $3}' | xargs docker rmi
複製程式碼

使用grep命令匹配到tag為none的image,awk是一個強大的文字分析工具,{print $3}表示列印出匹配到的每一行的第三個欄位,也就是docker的image id。如果是$0的話表示當前整行的資料。

xargs是一個給其他命令(也就是後面的docker rmi)傳遞引數的一個過濾器,將標準輸入轉換成命令列引數。

總結來說,上述命令就是找到tag為none的image的ID,然後使用docker rmi命令移除該image。

4.6. 執行部署

cmd="cd ~ && sh deploy.sh mine"
ssh -t USER_NAME@IP_ADDRESS "bash -c \"${cmd}\""
複製程式碼

通過ssh命令,登入遠端伺服器,並且執行引數中的指令碼。

deploy.sh是放在服務端的構建指令碼。放在預設的登入使用者下。我們發現,後面還跟了個mine,這是在伺服器上執行的docker映象的名字。這裡暫時沒有對container的名字加上hash,因為自己的小專案,暫時沒有必要。

在專案中的完整構建指令碼如下。

#!/bin/bash

if [ -d "./dist" ]; then
    rm -rf ./dist
fi
if [ -f "./Dockerfile" ]; then
    rm -f ./Dockerfile
fi

yarn build

echo "FROM nginx:latest" >> ./Dockerfile
echo "COPY ./dist /usr/share/nginx/html/" >> ./Dockerfile
echo "COPY ./default.conf /etc/nginx/conf.d/" >> ./Dockerfile
echo "EXPOSE 80" >> ./Dockerfile

docker build -t detectivehlh/mine .
docker login -u detectivehlh -p ********
docker push detectivehlh/mine

docker images | grep none | awk '{print $3}' | xargs docker rmi

cmd="cd ~ && sh deploy.sh mine"
ssh -t USER_NAME@IP_ADDRESS "bash -c \"${cmd}\""
複製程式碼

5. 編寫伺服器部署指令碼

從上面步驟來看,我們還需要一個伺服器端的部署指令碼。大家可能會說,標題不是說一個指令碼搞定嗎?em。。。伺服器一個,本地一個...簡稱只需一個指令碼。

5.1. 接收引數

在本地的構建指令碼中,我們傳入了docker執行的container的名字。在伺服器構建指令碼中需要來接收它。然後更新剛剛推送的docker image。

#!/bin/bash
name=$1
docker pull detectivehlh/$name
複製程式碼

5.2. 啟動container

在啟動container時我們會面對兩種情況,名字為傳入引數的container已經在執行了。而在此時如果再次執行docker run命令就會報錯而導致我們無法使用最新的container,也無法達到更新應用的目的。

if docker ps | grep $name | awk {'print $(NF)'} | grep -Fx $name; then
	echo "Container mine is already start"
	docker stop $name
	docker rm $name
	docker run -d --name $name -p 3000:80 detectivehlh/$name
else
	echo "Container mine is not start!, starting"
	docker run -d --name $name -p 3000:80 detectivehlh/$name
	echo "Finish starting"
fi
docker images | grep none | awk '{print $3}' | xargs docker rmi
複製程式碼

所以在這裡做一個判斷,第一個if判斷如果存在名字為傳入引數的container正在執行,就停止當前容器再重新啟動。如果不存在則直接啟動容器。

run命令就不過多解釋了。-d表示後臺執行容器並返回容器ID,--name表示設定容器的名字,-p表示設定埠,將阿里雲伺服器的3000埠對映到容器的80埠,最後一句表示要啟動哪個image(好像還是解釋了一遍)。

最後一句就是移除多次更新後出現的tag為none的無用映象。完整的指令碼如下。

#!/bin/bash
name=$1
docker pull detectivehlh/$name
if docker ps | grep $name | awk {'print $(NF)'} | grep -Fx $name; then
	echo "Container mine is already start"
	docker stop $name
	docker rm $name
	docker run -d --name $name -p 3000:80 detectivehlh/$name
else
	echo "Container mine is not start!, starting"
	docker run -d --name $name -p 3000:80 detectivehlh/$name
	echo "Finish starting"
fi
docker images | grep none | awk '{print $3}' | xargs docker rmi
複製程式碼

6. 如果你只是想打個包

看到標題進來的兄dei,如果只是想打包一個docker映象,那麼你只需要Dockerfile檔案和docker build命令就OK了。

7. 總結

最初寫這個指令碼,主要目的是為了方便。所以指令碼中為了達到這個目的做了一些調整。最終我達成了滿足我需求的一個方便的部署指令碼。

它的方便體現在,當我完成了專案程式碼的更新,只需要跑一下這個指令碼,然後等待一會兒,專案就會自動打包成docker image,並且自動的在我的伺服器上執行該container。

但是這種方式會給實際的生產環境帶來一些不可控的問題。比如,指令碼必須不能上傳,因為涉及一些伺服器的敏感資訊。但是如果你不小心上傳了,那你的伺服器就相當於裸奔了;再比如,你對你的程式碼必須要十分自信,沒有經過測試的程式碼就直接部署,會帶來一些風險。

如果是自己用的,那完全不用擔心,想怎麼搞怎麼搞。但是如果是開放給所有人用的並且有一定的訪問量,比如部落格,那麼對於其他使用者來說,這種方式就不怎麼友好。

所以我的觀點是,分情況來。目前來說我的專案只有少數幾個人在用,也還在處於迭代階段。並且程式碼倉庫是私有的,所以我完全不用擔心隱私的問題。服務未經測試就直接上線對於我來說,其實問題也不大。首先我會在本地測試,確認無誤後才會執行部署操作。所以在不同的階段,找到最適合自己的方案就OK。

相關文章