本篇程式碼
服務整體架構
開始之前我們先來看要佈署的架構:
本篇算是第一次使用 Terraform 佈署漫畫爬蟲以及漫畫閱讀器,所以我們單純使用一台EC2作為我們的執行環境。
爬蟲是透過 crontab 來重複定時啟動,爬取指定數量的新集數存入資料庫後關閉。閱讀器則是一個透過 python3 實現的單純 http 服務(Flask)。
程式碼架構
由於這次新增了不少程式碼,這裡本藻重新介紹一次專案的結構:
├── db_build_sample.sql # 建立comics/episodes/images用的.sql
├── env # 佈署用程式碼的資料夾(內容下段介紹)
│ ├── outputs.tf
│ ├── providers.tf
│ ├── resources_ec2.tf
│ ├── terraform.tfvars
│ ├── terraform.tfvars.sample
│ └── variables.tf
├── [proxy].pem # 用於登入外部機器,建立 ssh tunnel
├── README.md
├── requirements.txt # 專案 python 的相依性
├── [ec2].pem # 用來登入建立好的 EC2 用的 pem, 就是於
│ # AWS 網頁建立 keypair 後下載的東西
└── src # 內含漫畫爬蟲與閱讀器程式碼
├── api.py # 閱讀器後端
├── crawler_main.py # 爬蟲主程式
├── crawler_utils.py # 爬蟲需要的一些 lib
├── index.html # 閱讀器前端
└── storage.py # 同時給爬蟲與閱讀器使用的資料庫存取 lib
可以看出,比起 section_11,這次新增了用於佈署的 env
以及其他相關的設定檔(一個 .sql
兩個 .pem
)。
佈署程式解說
接著我們來介紹 env
底下的各個 .tf
檔:
variables.tf
基本上都跟之前介紹的差不多,只是針對 db_sql_path
要稍微補充一下:
用來初始化的.sql
可以不只包含 comics/episode/images 的 table 建立,同樣可以在這裡寫 INSERT
語法把要下載的漫畫一併放進去,總之就是個正常的 .sql
檔,關於其的詳細用法可以看以下:
providers.tf
之前說過,就是指定提供雲端服務的實體,這裡同樣使用 aws
。
outputs.tf
與之前相同,就是輸出最後建立的 ec2 的對外域名。
resources_ec2.tf
— (ec2之外的部份)
ec2 之外的 resources 定義有點長,所以這邊切成定義資料(data
, locals
)與 VPC 與網路環境(vpc
, routing
, security group
)定義的兩部份:
- Data/Locals
正如上一篇所介紹,data
在這裡是抓取可用的 availability zone
與 ami
。
locals
則是定義區域變數,src
放的是 env 的上層資料夾的絕對路徑,而 ssh_cmd
則是為求後續方便,把之後會用到的建立 ssh tunnel 指令變成一個字串變數。
眼尖的讀者可能會發現這邊使用 <<-EOT
來輸入多行變數,比起 <<EOT
多了個 -
,兩者的差距是每行前面的空白會不會被省略,這邊也就是為了排版漂亮吧,關於兩者的 terraform 支援可以看這邊:
- Networking resources
每次這裡都會變得很長,本藻其實也放棄了。vpc
igw
subnet
rtb
rtb-subnet
的部份基本已經變成套組了,這邊的解釋就先跳過吧。這邊最重要的是記得設定 aws_security_group
的 ingress/egress 規則,給連入 port 22(ssh), 3306(Mysql), 5000(web service) 解除防火牆限制,同時因為要跑爬蟲,解除對連出的所有限制(port=0 就是全部的 port, 0.0.0.0/0 就是 ipv4 的全網)。
resources_ec2.tf
— (ec2的部份)
然後我們的 EC2 定義真的很長,這裡分為除 remote-exec 的其他設定與 remote-exec 的兩部份:
- 除 remote-exec 的其他設定(EC2 設定)
可以看到,這邊就是先設定 EC2 必要參數 ami
instance_type
subnet_id
vpc_security_group_ids
key_name
。然後使用 connection
指定接下來的檔案複製與初始化要使用的連線,顯然本藻用的是 ssh,所以記得把 private_key_path 檔案中的內容用 file 丟進去。
另外需要特別介紹的是 provisioner "file"
以及 provisioner "remote-exec"
,前者用於上傳本機檔案到遠端的 ec2,後者則是用在於遠端 ec2 執行佈署指令(shell script)。由於這邊的 remote-exec
內容有點複雜,本藻放在下個部份。另外,關於 ec2 的其他設定看官方教學,而如果對 provisioner 想了解更多的則可以看這個官方教學。
- remote-exec(初始化 scripts) 很長
這裡就到我們的重頭戲了,一個 EC2 起來的時候裡面實際上就跟一個全新的系統一樣,所以在啟動我們的爬蟲與網頁之前得先處理一些相依性的各種雜事,因此這邊把 remote-exec
內部的程式碼分成「相依性安裝」、「網頁初始化與啟動」與「爬蟲初始化與註冊定時啟動」三個部分。
特別注意:爬蟲因為其本身的特性,我們並不需要它常駐啟動,所以這邊我們採用定時週期啟動的方式(如果有一些 web hook 或 rss 可用的話也可考慮事件驅動)。
「相依性安裝」:
可以發現,這邊除了熟悉的 apt-get
還用到了一個 while [ ! -f …/boot-finished ]; do
的迴圈。首先, /var/lib/cloud/instance/boot-finished
這檔案存在表示 EC2 順利開機完成,反之,就是還沒開好。
也許你會想説「都能 ssh 進去了,機器應該都開好了吧?」,但很抱歉答案當然是 No。本藻也不知道為什麼機器都還沒開好 AWS 就讓你連進去,但本藻知道,如果沒有等到那個神奇檔案出現(就是開機開好)
網路會連不出去
所以之後配置請不要忘記。
「網頁初始化與啟動」:
這裡主要包含兩個動作:
資料庫設定變更與初始化
- 把
/etc/mysql/mysql.conf.d/mysqld.cnf
中要 bind 的 ip 從只針對本機的 127.0.0.1 改為可讓外網存取的 0.0.0.0 - 透過剛剛傳進來的 .sql 建立必要 table 與插入資料
- 建立可從外部連線的 db 使用者
啟動漫畫閱讀器
- 移動到剛剛
provisioner "file"
複製的專案資料夾安裝 requirements.txt - 設定資料庫連線參數(用 export)
- 啟動服務(用 nohup 掛背景運行,不然 terminal 關掉後服務就會死去)
整體來說並沒有太難理解。
「爬蟲初始化與註冊定時啟動」:
總之這裡主要包含三個部分:「生成一鍵啟動 ssh tunnel 的 script」、「安裝可用 chrome 與 chromedriver」與最後的「將爬蟲啟動 script 插入 crontab」,以下個別拆解。
生成一鍵啟動 ssh tunnel 的 script
- 把剛剛用
provisioner "file"
上傳進來的proxy.pem
權限設為 600(否則 ssh 連線時會告訴你太開放(too-open) 然後失敗) - 生成
launch_proxy.sh
(locals
那邊定義的ssh_cmd
就在這裡使用) - 給
launch_proxy.sh
加上執行權限
安裝可用 chrome 與 chromedriver
- 從 google-chrome 官方選擇固定版本的 chrome 下載(使用 chrome 非常重要,不是 chromium,本藻在寫這篇的時候被 chromium 莫名其妙的問題雷到體無完膚)
- 安裝載下來的
.deb
(一定要用 apt install, 用 dpkg -i 會遇到相依性無法安裝的問題,然後死去) - 依據 chrome 的版本下載對應的 chromedriver
- 把 chromedriver 複製到
/home/ubuntu/.local/bin
(就是把程式移到$PATH
包含的路徑)
最後關於將爬蟲啟動 script 插入 crontab的部分,其實也就是 echo XXXX | crontab -
完全取代 crontab 內容的一點變化,只是後面的 echo 被換成一個 ()
包起來的一坨程式碼的輸出。()
內部的詳細如下:
- 使用
crontab -l 2>/dev/null
輸出既有的 crontab 內容(並避免錯誤的輸出) - 使用 echo 添加新一行 task(依照 crontab 的格式,同時
launch_proxy.sh
與資料庫連線的必要參數設定(export)必須在爬蟲啟動前)
至此希望還算好理解。雖然很長,但總之這樣就寫完必要的檔案了。
執行 & 測試
最後,透過 terraform init
terraform plan -out dev.tfplan
terraform plan dev.tfplan
部署看到以下就算成功了:
網頁
打開瀏覽器連線剛剛 terraform 輸出的域名後面接上 :5000
。
爬蟲
把 crontab 裡面的指令複製出來直接執行。
結語
恭喜你又看完了一篇海藻的自虐。這次算是第一次深入使用 ec2,可以發現隨著越深入使用 AWS,自虐的地方越來越多了。不過請不要擔心,一時用 AWS 一時虐,一直用 AWS 一直虐。我們會繼續虐下去,在下篇我們將要另外引入一台 RDS 當資料庫,敬請期待吧。
下篇預告
資料庫很重要,所以很貴 — AWS RDS