前端學習Docker
文章參考知乎,花生部落格。記錄下docker的一些知識
Docker
沒有使用docker前,我們經常遇到如下問題:
- 手動部署成本太高,改錯別字都很麻煩
- 一臺伺服器由於時間累積導致環境變得“髒亂差”
- 重灌系統成本太高,難以遷移
那麼 Docker 是如何做的呢?
映象與容器
Docker 中有兩個重要概念。
一個是容器(Container):容器特別像一個虛擬機器,容器中執行著一個完整的作業系統。可以在容器中裝 Nodejs,可以執行npm install
,可以做一切你當前作業系統能做的事情
另一個是映象(Image):映象是一個檔案,它是用來建立容器的。如果你有裝過 Windows 作業系統,那麼 Docker 映象特別像“Win7純淨版.rar”檔案
上邊就是你所需要了解的 Docker 全部基礎知識。就這麼簡單
順便一提,在 Docker 中,我們通常稱你當前使用的真實作業系統為“宿主機”(Host)
安裝 Docker
安裝 Docker 在你的電腦上就像安裝 VS Code 一樣簡單
如果你使用的是Windows電腦,需要購買支援虛擬化的版本。如Win10專業版,Win10家庭版是不行的
- Mac:https://download.docker.com/mac/stable/Docker.dmg
- Windows:https://download.docker.com/win/stable/Docker%20for%20Windows%20Installer.exe
- Linux:https://get.docker.com/
安裝完Docker後,你可能會發現自己可以開啟一個漂亮的 Docker 視窗。其實這個視窗沒什麼用處,通常我們都是通過CLI命令列的方式操作 Docker的,就像 Git 一樣
執行 Docker
接下來我們搭建一個能夠託管靜態檔案的 Nginx 伺服器
容器執行程式,而容器哪來的呢?容器是映象創建出來的。那映象又是哪來的呢?
映象是通過一個 Dockerfile 打包來的,它非常像我們前端的package.json
檔案
所以建立關係為:
Dockerfile: 類似於“package.json” | V Image: 類似於“Win7純淨版.rar” | V Container: 一個完整作業系統
建立檔案
我們建立一個目錄hello-docker
,在目錄中建立一個index.html
檔案,內容為:
<h1>Hello docker</h1>
然後再在目錄中建立一個Dockerfile
檔案,內容為:
FROM nginx COPY ./index.html /usr/share/nginx/html/index.html EXPOSE 80
此時,你的檔案結構應該是:
hello-docker |____index.html |____Dockerfile
打包映象
檔案建立好了,現在我們就可以根據Dockerfile
建立映象了!
在命令列中(Windows優先使用PowerShell)鍵入:
cd hello-docker/ # 進入剛剛的目錄 docker image build ./ -t hello-docker:1.0.0 # 打包映象
注意!Docker 中的選項(Options)放的位置非常有講究,docker —help image
和docker image —help
是完全不同的命令
docker image build ./ -t hello-docker:1.0.0
的意思是:基於路徑./
(當前路徑)打包一個映象,映象的名字是hello-docker
,版本號是1.0.0
。該命令會自動尋找Dockerfile
來打包出一個映象
Tips: 你可以使用docker images
來檢視本機已有的映象
不出意外,你應該能得到如下輸出:
Sending build context to Docker daemon 3.072kB Step 1/3 : FROM nginx ---> 5a3221f0137b Step 2/3 : COPY ./index.html /usr/share/nginx/html/index.html ---> 1c433edd5891 Step 3/3 : EXPOSE 80 ---> Running in c2ff9ec2e945 Removing intermediate container c2ff9ec2e945 ---> f6a472c1b0a0 Successfully built f6a472c1b0a0 Successfully tagged hello-docker:1.0.0
可以看到其運行了 Dockerfile 中的內容,現在我們簡單拆解下:
FROM nginx
:基於哪個映象COPY ./index.html /usr/share/nginx/html/index.html
:將宿主機中的./index.html
檔案複製進容器裡的/usr/share/nginx/html/index.html
EXPOSE 80
:容器對外暴露80埠
執行容器
我們剛剛使用 Dockerfile 建立了一個映象。現在有映象了,接下來要根據映象建立容器:
docker container create -p 2333:80 hello-docker:1.0.0 docker container start xxx # xxx 為上一條命令執行得到的結果
然後在瀏覽器開啟127.0.0.1:2333
,你應該能看到剛剛自己寫的index.html
內容
在上邊第一個命令中,我們使用docker container create
來建立基於hello-docker:1.0.0
映象的一個容器,使用-p
來指定埠繫結——將容器中的80
埠繫結在宿主機的2333
埠。執行完該命令,會返回一個容器ID
而第二個命令,則是啟動這個容器
啟動後,就能通過訪問本機的2333
埠來達到訪問容器內80
埠的效果了
Tips: 你可以使用docker containers ls
來檢視當前執行的容器
當容器執行後,可以通過如下命令進入容器內部:
docker container exec -it xxx /bin/bash # xxx 為容器ID
原理實際上是啟動了容器內的/bin/bash
,此時你就可以通過bash shell
與容器內互動了。就像遠端連線了SSH一樣
發生了什麼
我們總結下都發生了什麼:
- 寫一個 Dockerfile
- 使用
docker image build
來將Dockerfile
打包成映象 - 使用
docker container create
來根據映象建立一個容器 - 使用
docker container start
來啟動一個建立好的容器
雖然很簡單,但是也沒有感覺到“廣闊天地,大有可為,為所欲為”呢?
遷移靜態站點
接下來我們實戰遷移一個由 Vuejs 寫的純靜態 SPA 單頁站點:
我打算怎麼做
在沒遷移 Docker 之前,若我想更新線上網站中內容時,需要:
- 本地
npm run build
打包產出靜態檔案 - 手動通過 FTP 上傳到伺服器
git push
更新 Github 原始碼
稍微有點麻煩,因此我打算這樣改:
- 執行
git push
- 自動檢測到 github 有程式碼更新,自動打包出一個 Docker 映象
- CI 編譯完成後,SSH 登入 VPS,刪掉現有容器,用新映象建立一個新容器
而這樣做的好處是:
- 不必再手動 FTP 上傳檔案
- 當我進行修改錯別字這樣的簡單操作時,可以免測。改完直接
git push
,而不必本地npm run build
Github中的CI
首先是讓 Github 在我每次更新程式碼時打包出一個映象
在 Github,可以有免費的 CI 資源用,它就是Travis CI
在專案中根目錄中新增.travis.yml
檔案,內容如下:
language: node_js node_js: - "12" services: - docker before_install: - npm install script: - npm run build - echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin - docker build -t pea3nut/pea3nut-info:latest . - docker push pea3nut/pea3nut-info:latest
檔案內容非常簡單,就是使用npm run build
編譯靜態產出後,打包一個映象並且 push 到遠端。有幾點需要詳細說一下:
- 為了能夠讓映象上傳到伺服器,你需要在
hub.docker.com
中註冊一個賬號,然後替換程式碼中的pea3nut/pea3nut-info:latest
為使用者名稱/包名:latest
即可 - 使用 Github 登入 Travis CI 後,在左邊點選+加號新增自己的 Github 倉庫後,需要移步到 Setting 為專案新增
DOCKER_USERNAME
和DOCKER_PASSWORD
環境變數。這樣保證我們可以祕密的登入 Docker Hub 而不被其他人看到自己的密碼。如下圖
然後需要新增 Dockerfile 檔案來描述如何打包 Docker 映象。
按照.travis.yml
的命令次序,在打包映象時,npm run build
已經執行過了,專案產出已經有了。不必在 Docker 容器中執行npm install
和npm run build
之類的,直接複製檔案即可:
FROM nginx COPY ./dist/ /usr/share/nginx/html/ EXPOSE 80
Note: 過程雖然簡單但是線條很長,建議本地多測試測試再進行git push
若你編譯出的靜態站點也是一個 SPA 單頁應用,需要增加額外的 Nginx 配置來保證請求都能打到index.html
。下邊是我寫的vhost.nginx.conf
Nginx 配置檔案,將不訪問檔案的請求全部重定向到/index.html
:
server { listen 80; server_name localhost; location / { root /usr/share/nginx/html; index index.html index.htm; proxy_set_header Host $host; if (!-f $request_filename) { rewrite ^.*$ /index.html break; } } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } }
然後在 Dockerfile 中新加一行,將本機的vhost.nginx.conf
檔案複製到容器的/etc/nginx/conf.d/pea3nut-info.conf
,讓 Nginx 能夠讀取該配置檔案:
FROM nginx COPY ./dist/ /usr/share/nginx/html/ + COPY ./vhost.nginx.conf /etc/nginx/conf.d/pea3nut-info.conf EXPOSE 80
然後執行git push
後,你可以在 Travis CI 看到 CI 的編譯結果。如果編譯沒問題,遠端實際上就有了pea3nut/pea3nut-info:latest
這個映象。本地可以試試看該映象工作是否正常:
docker image pull pea3nut/pea3nut-info:latest docker container create -p 8082:80 pea3nut/pea3nut-info:latest docker container start xxx # xxx 為上一條命令執行的返回值
執行完成後,瀏覽器訪問127.0.0.1:8082
應該就能看到效果了!
然後你可以登入遠端 VPS 伺服器,安裝 Docker,執行同樣的命令。然後訪問遠端 VPS 伺服器的公網 IP + 8082 埠號,應該能看到和本地相同的效果
Tips: 忘了如何在 VPS 上安裝 Docker?在上文“安裝 Docker”一節,你可能需要的是 Linux 的安裝方式bash curl https://get.docker.com/ > install-docker.sh # 下載安裝指令碼 sh install-docker.sh # 執行安裝指令碼
Nginx 反向代理
Note: 接下來的操作都是在你的遠端 VPS 伺服器上操作,並非本地電腦,或者容器中
目前我們將容器掛到了 8082 埠,但是線上不可能讓使用者手動輸入 8082 埠進行訪問。而如果將容器直接掛到 80 埠,雖然這樣使用者可以直接不加埠直接訪問,但是如果有第二個容器,或者更多容器呢?
這時候就需要在宿主機跑一個 Nginx,由它來獨佔 80 埠,然後根據域名來講請求分發給響應的容器。如下圖:
這種方案叫做“反向代理”
登入VPS伺服器,安裝 Nginx。因為我是 Ubuntu,所以可以用apt
安裝。其他 Linux 發行版可以百度下安裝方法,通常2行內可以搞定:
apt update # 更新軟體包 apt-get install nginx # 安裝 Nginx systemctl status nginx # 檢視 Nginx 狀態
此時本地通過瀏覽器訪問 VPS 的公網 IP 可用看到 Nginx 的歡迎頁面
然後在 VPS 伺服器的/etc/nginx/conf.d/
中建立一個vhost.conf
檔案,配置如下內容:
server { listen 80; server_name pea3nut.info; location / { proxy_pass http://127.0.0.1:8082; } }
配置的意思是,監聽來自 80 埠的流量,若訪問域名是pea3nut.info
(替換為你自己的域名),則全部轉發到http://127.0.0.1:8082
中
配置完成後,重啟 Nginx 伺服器。若是 Ubuntu 可以使用systemctl restart nginx
命令,不同 Linux 發行版稍有不同
配置成功後,訪問pea3nut.info
會看到和VPS公網IP:8082
相同的效果
更新站點
而遷移完成 Docker 後,我想改一個錯別字的流程變為:
- 本地修改完成,執行
git push
- 等待 CI 編譯完成
- 登入 VPS 伺服器,執行:
docker image pull pea3nut/pea3nut-info:latest docker container create -p 8082:80 pea3nut/pea3nut-info:latest # 得到 yyy docker container stop xxx # xxx 為當前執行的容器ID,可用 docker container ls 檢視 docker container start yyy # yyy 第二條命令返回值
命令還是有些長?我們在下面會進一步優化它
遷移 Nodejs 站點(Express)
接下來我們實戰遷移一個由 Nodejs 寫的 Express SSR 站點
我打算怎麼做
網站使用 Ejs 模板渲染頁面。在沒遷移 Docker 之前,若我想更新線上網站中內容時,需要:
- 本地修改好 Ejs 或者其他檔案
- 手動通過 FTP 上傳到伺服器
- 在伺服器端重啟 Nodejs 程序。若有 npm 包依賴改動,需要在VPS伺服器上手動執行
npm install
git push
更新 Github 原始碼
稍微有點麻煩,因此我打算這樣改:
- 執行
git push
- 自動檢測到 github 有程式碼更新,自動打包出一個 Docker 映象
- CI 編譯完成後,SSH 登入 VPS,刪掉現有容器,用新映象建立一個新容器
而這樣做的好處是:
- 不必再手動 FTP 上傳檔案
- 不必手動維護伺服器的 Nodejs 執行環境
實施
具體的過程和處理靜態站點沒有什麼特別的區別,無非是:
- 編寫 Dockerfile 檔案
- 在 CI 時自動打包映象
- 在VPS增加一個 Nginx 反向代理
這次就不重複講了,具體的配置可以參考專案中的相關檔案
Tips: 你可能發現了 Dockerfile 中的ENTRYPOINT
命令必須指定一個前臺程序。若你的 Nodejs 應用是使用 PM2 進行保活的,你需要替換pm2 start app.js
為pm2-docker app.js
docker-compose
當將 Nodejs 站點遷移完成,我們的 VPS 伺服器上已經運行了2個容器。每次映象更新都要手動的docker container create
帶一堆引數是比較麻煩的,尤其是當日後容器日益增多的時候。而這時,就輪到docker-compose
登場了~
docker-compose 是 Docker 官方提供的一個 Docker 管理工具。若你是通過桌面端的 Docker 安裝包安裝的 Docker,它是會預設為你安裝 docker-compose 的。可以試試如下命令:
docker-compose --help
如果是在 Linux,可以通過如下命令安裝 docker-compose:
curl -L "https://github.com/docker/compose/releases/download/1.23.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose chmod +x /usr/local/bin/docker-compose
docker-compose 和 Docker 差不多,也是隻要一份檔案就能跑起來。docker-compose 主要的作用就是能夠讓你不必手敲那麼多 Docker 命令
建立一個目錄,然後在目錄中建立docker-compose.yml
,內容如下:
version: "3.7" # 這個是配置檔案的版本,不同的版本號宣告方式會有細微的不同 services: info: container_name: pea3nut-info image: pea3nut/pea3nut-info:latest ports: - "8082:80" restart: on-failure
然後在目錄中鍵入如下命令就能將服務跑起來:
docker-compose up info
docker-compose 會幫我們自動去拉映象,建立容器,將容器中的80
埠對映為宿主機的8082
埠。restart
欄位還要求 docker-compose 當發現容器意外掛掉時重新啟動容器,類似於 pm2,所以你不必再在容器內使用 pm2
如果想要更新一個映象建立新容器,只需要:
docker-compose pull info docker-compose stop info docker-compose rm info docker-compose up -d info # -d 代表後臺執行
筆者已將自己網站部署方式開源,可參考github/pea3nut-hub
遷移 WordPress 站點(Apache + PHP + MySQL)
接下來我們實戰遷移一個 WordPress 站點
- 網址:pea3nut.blog
- 原始碼:非公開
可能你也發現了這個站點和其他站點的一個非常大的不同——他的原始碼和資料是不能公開的
之前我們打包映象時,都是直接將程式碼打進映象內的。這條方案用在這裡顯然是不行的,有兩個問題:
- 我不想公開 MySQL 資料檔案和網站內容(如圖片)。若將這些打包進映象,任何人都能
docker image pull
下載到映象,然後取得映象內的檔案 - 當容器被刪掉,儲存的 MySQL 資料都將丟失
Volume
Docker 提供了一個叫做 Volume 的東西,可以將容器內和宿主機的某個資料夾進行”繫結“,任何檔案改動都會得到同步。所以,我可以將整個站點目錄和 MySQL 目錄都掛載為 Volume。這樣,當容器刪除時,所有資料檔案和原始碼都會保留。
在本地建立./blog/mysql-data
目錄儲存 MySQL 資料,建立./blog/wordpress
目錄儲存 WordPress 原始碼。然後修改docker-compose.yml
如下:
version: "3.7" services: info: container_name: pea3nut-info image: pea3nut/pea3nut-info:latest ports: - "8082:80" restart: on-failure + blog: + container_name: pea3nut-blog + image: tutum/lamp:latest + ports: + - "8081:80" + volumes: + - ./blog/mysql-data:/var/lib/mysql + - ./blog/wordpress:/app + restart: on-failure
可以看到這次根本沒有打包映象,而是直接使用tutum/lamp
映象提供的 LAMP 環境(Linux + Apache + MySQL + PHP),然後將 MySQL 資料目錄/var/lib/mysql
和原始碼目錄/app
都掛載出來就可以了
Tips: 通過 Volume 我們只是解決了部署問題,而如何本地開發然後將原始碼同步到伺服器呢?用 FTP 當然是可以的,但是稍微有點麻煩。其實你可以自建一個 Git 伺服器!