筆記 - Docker 與 AWS EC2 初探

近期搜尋職缺,很常看到企業希望後端工程師求職者有部署與 AWS/GCP 這類雲端工具的使用經驗,這方面在之前 AC 課程中只有部署在 Heroku 的經驗,所以花點時間來試試。

在收集資訊的時候也發現雲端部署也常出現 Docker 容器化這個名詞,也一併研究與記錄下來。

Docker 容器化

Docker 是一種容器化工具,可以將應用程式打包裝進容器 (container) 裡,其中包含程式本身、所需要的 library 、執行環境 (runtime) 等。降低對運行裝置上的作業系統的 dependency。一般工程師開發環境可能是 macOS 或 Windows 作業系統,而商用伺服器通常是 Linux 系統。而容器化就是在降低不同開發環境下運行的需求差異,方便部署與管理。

另外,部署至雲端伺服器上後,也能與其他應用程式隔離,避免影響到其他程式的運行。其他容器化工具還有 Kubernetes 等。

Docker 流程

單一 container 的容器化流程:

Dockerfile => 建立 Docker Image => 在 Docker Container 中運行

單一 container 的流程


Dockerfile

Dockerfile 內需撰寫環境設定的指令,包含 node 版本、要複製哪些資料夾/檔案(可以設定 layers 讓設定一步步接續執行)、運行 command 、指定 port 等。

其中注意 FROM node:<version>-alpine
參考官方文件Docker docs: dockerfile

Dockerfile 寫好之後,別忘了建立 .dockerignore 檔案,跟 .gitignore 一樣放入不想被打包進去 image 的檔案(例如: .git, .env, node_modules等)

建立 image (以網頁記帳本專案為例)。Image naming convention: user-name/image-name

docker build -t rubylo718/expense-tracker .

Image


Docker Image

Image 包含了所有程式運行所需要套件工具。建立好 image 後,透過 docker run 指令試著運行看看。

docker run -it -p 3000:3000 rubylo718/expense-tracker

環境變數帶入

由於網頁記帳本專案中,是使用 dotenv 管理環境變數,主要是 MongoDB 連線所需 URI 與登入功能的 session secret 。但環境變數可能包含機敏資訊,很自然地放在 .dockerignore 中沒打包進去 image 裡。此時 docker run 就會因為缺少環境變數無法連線 MongoDB 而報錯。

Docker 運行帶入環境變數的方法有 3 種:

  1. ENV VARIABLE=value 放在 Dockerfile 裡(阿捏不就是明文!?)
  2. 運行指令 docker run 放入 --env or -e 帶入,例如 docker run --env VARIABLE=value ...
  3. 運行指令 docker run 直接帶入檔案 docker run --env-file .env ... (可能只適合本機端運行)

參考:docker docs: set env variables | Medium 環境變數

Run Docker Container from the Image

因為本來就有 .env 檔案了,所以採第三種方式帶入成功運行起來~

docker run --env-file .env -p 3000:3000 rubylo718/expense-tracker

Image


Docker Hub

Docker Hub 是像 Git Hub 一樣的雲端儲存庫,可以把自己的 Docker Image 推上去。也方便之後從 AWS 環境連線到 Docker Hub 將 image pull 過去。

docker push rubylo718/expense-tracker

Image


AWS EC2

AWC EC2 的全名是 Amazon Web Services Elastic Compute Cloud。AWS 指亞馬遜的雲端運算服務,EC2 指可以讓使用者租用雲端運行所需要的系統,可以在虛擬機器上運行任何自己的軟體或應用程式。這邊的 Elastic 彈性的意思是使用者可以隨時建立、執行、終止自己的虛擬伺服器,用多少算多少費用。以上參考維基百科

AWS EC2 部署流程

註冊 account => Launch instance => Connect to instance => Install Docker on the instance => Login Docker and run the image on DockerHub

Launch instance

註冊完,選擇 AWS EC2 服務進去按 Launch instance 開始一連串的設定:

1. Amazon Machine Image (AMI)

選免費的選項,選擇同時包含 x86 與 arm ,Architecture 這邊目前不太清楚是否有差異。

2. Instance type

免費選項是 micro 也足夠使用

3. Key pair (重要)

這是一個稱為 public key encryption 的方法,設定好之後會產生一個 public key 放在你的 EC2 instance 上,另一個 private key 則下載保存在本機端。

注意! private key 檔案不要放在有 git 追蹤或是任何會 push 至雲端的資料夾中,屆時登入時會讀取本地端檔案(.pem 檔,但我實作下來是產生.cer 憑證檔,待研究)。

RSA 是一種加密的演算法。SSH protocol 全名為 Secure Shell Protocol,用來安全連接兩台任何通訊裝置。這邊先不深究,繼續做下去~

Image


4. Network settings

這邊前段都是預設設定,重點是 firewall (security groups),要設定控制通過 instance 的防火牆規則。共需要兩組:(1) Allow SSH traffic from anywhere. (使用前面設定的 key pair 驗證) (2) Add custom TCP rule from anywhere through port 3000. 這邊 port 要與 docker 設定使用的 port 相同。

HTTP 為應用層通訊方法,包含在 TCP 通訊中。參考維基百科

後續設定就照預設,不需修改。Launch 之後稍等一下就可以在 dashboard 中看到新建立的 instance。

Image


Connect to instance with SSH

在 dashboard 按下 connect 會跳出連接方法,在 SSH client tab 下會有提示。
打開 terminal ,打開 private key location folder,照指示貼上 command:

chmod 400 <key pair name>

只允許目前使用者存取 private key,此裝置上的其他使用者不可使用。另外也設定此 private key read only 不可修改。

ssh -i "<key pair name>" <user account>@<public IP address>
user account: ec2-user
-i tag 表示 identity file

看到以下畫面,就是成功透過 SSH 連接 EC2 instance。(那個形狀是什麼…看不懂)

Image


Install Docker on the instance

看到 terminal 提示就可以知道:AWS EC2 instance 環境是 Linux,安裝或更新程式或套件的指令是 yum

yum 是套件管理工具,類似我們在 Node.js環境下使用的 npm。

sudo yum update -y

先更新環境,確保環境是最新版本。
-sudo 代表以管理者身份使用者進行

sudo yum install docker

安裝 docker,會提示下載資料大小

sudo service docker start

在背景下啟動 docker

sudo usermod -a -G docker ec2-user

把 ec2-user 加入 docker group 就可以不用每次都請求管理者權限 sudo 來執行指令。跑完後 exit 離開 server 再重新連接。此時 ec2-user 可以使用 docker 指令。(try on docker info )

Deploy Docker Image to AWS

有幾種方式可以部署:

  1. Docker Compose (章魚圖案):可以部署複數個 containers
  2. Amazon ECS (Elastic Container Service): AWS 專門用來部署 image 的服務

目前單純只有一個 docker container 先直接部署在 EC2,使用免費方案就好。

Login DockerHub and Run

docker login
依照提示輸入帳號密碼登入 DockerHub ( docker image 在 DockerHub 上為 private 時需要)

docker run -p 3000:3000 rubylo718/expense-tracker

這邊踩到兩個雷,以下紀錄:

雷1: 執行環境不相容

跑起來先看到這個錯誤訊息,明明已經打包成容器了怎麼還是不相容呢?

1
WARNING: The requested image's platform (linux/arm64/v8) does not match the detected host platform (linux/amd64)

在我的裝置上(Apple M1 chip) docker 打包的 image 與目前的 instance 執行環境 linux/amd64 不相容。

丟進去 google 得到這個 stackoverflow 參考回答。我參考回答內容,把原來的 image 刪掉,重新用以下指令打包後,再推上 DockerHub

docker buildx build --platform=linux/amd64 -t rubylo718/expense-tracker .

可以在 DockerHub 上看到 image 檔案的 OS/ARCH 屬性質從原本的 linux/arm64/v8 變成 linux/amd64 了。解決。

事後想想有另一個可能可以解決方法:在 Launch instance 第一步 AMI Architecture 選項時選擇 arm64 。需要重新建立新的 instance 。

雷2: 環境變數設定(又是你)

解決了上面的執行環境之後,docker run 馬上會發現在 EC2 上執行沒有設定環境變數,所以缺少 URI 無法連結 MongoDB 報錯。

先前在本地端跑 docker image 的時候,是使用 docker run --env-file .env 帶入 .env 檔案內的環境變數。但是現在在雲端環境呀,怎麼帶檔案呢?

官方解法:最完善的解法是使用 AWS Secrets Manager 服務,儲存這些秘密,再帶入 EC2 instance 中。號稱很安全,但是要收費$$。另外兩個方法就同這篇文章先前提到 docker run --env 個別帶入,或是 docker run --env-file 檔案帶入。

我沒有使用付費服務,但雲端環境也沒想到怎麼帶入 .env 檔案,所以最後只好用 docker run -env 個別帶入了orz

docker run --env VAR1=value1 --env VAR2=value2 --env VAR3=value3 -p 3000:3000 rubylo718/expense-tracker

如此一來 EC2 可以順利收到環境變數,跑起來,登入進行操作也沒問題~

Image

Image

Image


感想

  1. 這次參考網路上的教學,搭配官方文件以及各種資訊。
  2. 參考的教學製作日期大概是2021年中,目前在 AWS 上看到的 UI 差別甚大。深感各項工具不斷進步中。
  3. 環境變數設定的問題在 AC 學期 2-3 學到資料庫連線時就遇到過,根據之前的經驗這次困難算還挺快速解決。覺得經過半年經驗累積過後的自己,遇到類似問題時解決的速度快很多!但目前是處理到能跑而已,或許沒有解決真正的問題,但目前就先處理到此了吧。

延伸 topics

  1. Public key encryption, .pem.cer
  2. 傳輸層,TCP,SSH Protocol
  3. 加密演算法 RSA,跟之前用過的 bcrypt 加鹽雜湊有什麼不同的應用
  4. CI/CD 如何運作,以及在 AWS 上如何進行