整體架構
- 使用 Terraform 開啟 EC2, Pipeline 服務 (terraform 開出 s3 artifacts)
- 使用 Ansible service role 自動化安裝服務
- 安裝 Codedeploy-agent
- 寫單體應用程式佈署腳本, scripts, service 檔案
- 寫 CodeBuild, Deploy 使用的腳本 appspec.yml, buildspec.yml
這一篇是想要利用 CodePipeline 服務達到: 把 Code 推到 staging or production 分支,就自動跑 Build,然後自動 Deploy 上線服務。
除此之外,EC2 的啟動、環境安裝都會透過 Terraform, Anaible 進行。
準備 Terraform - 模組
這裡使用的模組是本篇文章自己寫的 Terraform Module,模組請參考這個 Repo:
https://github.com/hpcslag/infrastructure_boilerplate/tree/main/terraform/modules/server
locals {
env = var.env
project_name = var.project_name
}
resource "aws_codedeploy_app" "app" {
compute_platform = "Server"
name = var.project_name
}
module "my_deployment_group" {
source = "./modules/deployment_group"
deployment_group_name = "my_deployment_group"
app_name = aws_codedeploy_app.app.name
}
module "my_server" {
source = "./modules/server"
count = length(var.env) # ["staging", "production"]
aws_region = var.aws_region
key_name = aws_key_pair.deployer.key_name
instance_type = length(regexall(".*production.*", var.env[count.index])) > 0 ? "t2.medium" : "t2.small"
volume_size = 20 # 20 GB
namespace = "my_server_${var.official_api_env[count.index]}"
deployment_group_name = "my_deployment_group"
}
要稍微注意,這裡啟動機器是用 list, ["staging", "production"],千萬不要莫名其妙減少一個值,這裡都是按照順序建立 server,很可能會因為增減搞壞,如果有個別處理需求,建議分開寫,不要加到 list 中。
服務寫好之後,直接啟動就會得到 CodeBuild 和 Pipeline,但還沒有辦法直接使用,要使用 Ansible 去機器安裝,但在 Ansible 安裝之前,要把機器資訊導出給 Ansible ,也就是要導出 ssh.config 和 hosts 檔案,而且要自動產生,避免太多人工。
寫一個 output.sh 方便處理
在寫一個 output.sh 之前,需要有一個 output.tf 的 output 定義,才能撈出資料:
https://github.com/hpcslag/infrastructure_boilerplate/tree/main/terraform/modules/server
output "my_server" {
value = flatten([
for data in module.my_server : {
public_ip = data.public_ip
namespace = data.namespace
}
])
}
這個寫法的意思是 my_server 是一個 counting 的 terraform modules,它就會把每一個建立出來的 EC2 自動填到這個 list 裡面。
因此你就可以建立一個腳本 output.sh 處理它,自動放到 ansible 目錄底下:
terraform output -json my_server | jq -r '.[] | "Host \(.namespace)
Hostname \(.public_ip)
User ubuntu
IdentityFile ~/.ssh/id_rsa"' >> ssh.config
echo "[my_server]" >> hosts
terraform output -json my_server | jq -r '.[] | "\(.namespace)"' >> hosts
執行之前,可能需要改一下權限,使用 sudo chmod +x output.sh 可以得到執行它的權限。
準備 Ansible 模組
在 ansible 目錄下,建立一個 roles 的資料夾,可以放入各種模組,在那之前我們需要先把一個模組拉回來,就是 codedeploy-agent,ec2 機器上一定要安裝這個模組, CodeDeploy 才會動,否則就無法佈署。
在這裡使用的是:
這個 role,安裝方式是直接把 git 目錄拉到 roles 底下,如果是用 ansible-galaxy 安裝,安裝後會拿到一個安裝位置,把那個安裝位置的資料夾直接 cp 或是 mv 過去。
我的 ansible roles 目錄就會像這樣:
roles
├── andrewrothstein.ipfs
│ ├── defaults
│ ├── handlers
│ ├── meta
│ ├── tasks
│ ├── tests
│ └── vars
├── andrewrothstein.unarchive-deps
│ ├── defaults
│ ├── meta
│ ├── tasks
│ └── vars
├── common
│ ├── defaults
│ └── tasks
├── diodonfrost.amazon_codedeploy
│ ├── defaults
│ ├── handlers
│ ├── meta
│ ├── molecule
│ │ ├── default
│ │ └── windows
│ ├── tasks
│ ├── tests
│ └── vars
├── fubarhouse.rust
...
主要目錄裡面有一個 ansible.cfg:
[defaults] inventory = ./hosts #vault_password_file = vault.key ansible_managed = Ansible managed, any changes you make here will be overwritten [ssh_connection] ssh_args = -o ControlMaster=auto -o ControlPersist=15m -F ssh.config -q scp_if_ssh = True
還有一個 hosts, ssh.config ,這兩個都是剛才 outputs.sh 產生出來的。
接著要寫一個 site.yml 當作 ansible playbook:
---
# This playbook is intended to install all the necessary dependencies of the
# application and set the remote server up for development
- name: Create user accounts for deployment and execution
hosts: all
remote_user: "{{ user_name }}"
become: true
tags:
- users
- install
roles:
- users
- name: Install CodeDeploy agent
hosts: all
remote_user: "{{ user_name }}"
become: true
tags:
- codedeploy
- install
roles:
- diodonfrost.amazon_codedeploy
- name: Tune journald settings
hosts: all
remote_user: "{{ user_name }}"
become: true
tags:
- journal
- install
roles:
- stuvusit.systemd-journald
- name: Install software
hosts: all
remote_user: "{{ user_name }}"
become: true
tags:
- deps # ansible-playbook -v site.yml --tags deps can make install to target machine
- install
roles:
- common
- name: Prepare Golang Service
hosts: all
remote_user: "{{ user_name }}"
become: true
tags:
- deps # ansible-playbook -v site.yml --tags deps can make install to target machine
- install
roles:
- role: gantsign.golang
golang_gopath: '$HOME/workspace-go'
golang_version: '1.17'
完成後,就可以直接執行安裝腳本指令:
ansible-playbook -v site.yml --tags install就可以完成安裝。
以上所使用的 roles 腳本,都可以在:
https://github.com/hpcslag/infrastructure_boilerplate/tree/main/ansible
這個專案中看到範例。
準備 Service 模板和 Nginx 設定等
上一節執行後,機器已經安裝好基本應用程式,但還需要安裝 Infrastructure 的部分,總共有幾個部分需要設定,Nginx 和自訂應用程式的 Service 檔案,以下 yml 是完整的設定:
- name: Setup Nginx
hosts: all
remote_user: "{{ user_name }}"
become: true
tags:
- install
- Nginx
roles:
- role: geerlingguy.nginx
nginx_service_state: started
nginx_service_enabled: true
nginx_vhosts:
- listen: "80 default_server"
filename: "my_project.vhost.conf"
server_name: "YOUR_DOMAIN.com"
state: present
extra_parameters: |
location / {
proxy_pass http://localhost:8080;
}
- name: Setup My Application
hosts: all
remote_user: "{{ user_name }}"
become: true
tags:
- services
- install
roles:
- role: services # 使用自訂服務,在 roles/services 下
vars:
app_type: MyApplication
# this requires aws configure to same region
- name: Localhost trigger code pipeline
hosts: all
tags:
- install
- pipeline
tasks:
- name: Trigger AWS Build
local_action: raw aws codepipeline start-pipeline-execution --name my-app-build-pipeline
上面的腳本都還無法執行,在這裡缺少了第二段 Setup My Application 的自訂服務建立,這個服務會自動幫我們安裝好應用程式的 Systemctl 模板。
此段請參考這個專案的幾個目錄:
https://github.com/hpcslag/infrastructure_boilerplate/tree/main/ansible
- templates
- my-application.j2
- services
基本上 services/task/service-my-application.yml 這個檔案就定義要使用 templates 資料夾中的 my-application.j2 當作 systemctl 模板,它將會把這個檔案複製過去。
裡面 j2 有許多模板變數可以被替換,只要根據需求更改 service-my-application.yml 就可以了。
第三段 yml 執行是 aws codepipeline,這是為了觸發將你的應用程式開始進行編譯,預期編譯完可以把程式放到你的機器上,這些在 terraform 中早已經有定義。
- name: Setup Nginx
hosts: all
remote_user: "{{ user_name }}"
become: true
tags:
- install
- Nginx
roles:
- role: geerlingguy.nginx
nginx_service_state: started
nginx_service_enabled: true
nginx_vhosts:
- listen: "80 default_server"
filename: "my_project.vhost.conf"
server_name: "YOUR_DOMAIN.com"
state: present
extra_parameters: |
location / {
proxy_pass http://localhost:8080;
}
- name: Setup My Application
hosts: all
remote_user: "{{ user_name }}"
become: true
tags:
- services
- install
roles:
- role: services # 使用自訂服務,在 roles/services 下
vars:
app_type: MyApplication
# this requires aws configure to same region
- name: Localhost trigger code pipeline
hosts: all
tags:
- install
- pipeline
tasks:
- name: Trigger AWS Build
local_action: raw aws codepipeline start-pipeline-execution --name my-app-build-pipeline
撰寫 CodeBuild 的 buildspec.yml
預設 CodeBuild 就會吃你 Repo 專案底下的 buildspec.yml 檔案進行處理,這裡的範例是:
version: 0.2
phases:
install:
commands:
- go mod download
build:
commands:
- go build
artifacts:
files:
- [YOUR APPLICATION BINARY FILE NAME]
- appspec.yml
- scripts/deploy-clean.sh
- scripts/deploy-install.sh
- .env
name: "go-server-$(date +%Y-%m-%d)"
discard-paths: yes
cache:
paths:
- /go/pkg/**/*
可以注意到上方 artifacts 是把某些檔案帶到下一個 Pipeline: CodeDeploy, scripts 目錄下的內容在這個資料夾中,需要把它放進專案
https://github.com/hpcslag/infrastructure_boilerplate/tree/main/scripts
它會依照 systemctl 的名稱進行應用程式重啟跟安裝,而且會保留 5 個版本。 這個腳本基本上就代表著更新應用程式。
撰寫 CodeDeploy 腳本 appspec.yml
當前面 buildspec.yml 完成建置之後,就會把剛才那些 artifacts 檔案帶到 appspec.yml,這裡就可以直接執行剛才帶來的檔案,完成最後佈署 + 更新。
範例指令檔案如下:
version: 0.0
os: linux
runas: deploy
files:
- source: /
destination: /tmp/go-deploy
hooks:
BeforeInstall:
- location: deploy-clean.sh
AfterInstall:
- location: deploy-install.sh
如此,這樣就算完成整個單體應用程式佈署流程了。
偵錯 CodeDeploy, CodeBuild
以下是一些我碰過的 CodeDeploy 錯誤問題跟可能找出問題的方式:
- CodeDeploy 連 step 1 都進不去就掛掉,而且錯誤訊息是空的
- 這表示你可能沒有在你的機器上安裝 CodeDeploy Agent
- 查看 journalctl -xefu codedeploy-agent 的 log
- 查看佈署錯誤紀錄: tail /var/log/aws/codedeploy-agent/codedeploy-agent.log
- 執行到下載階段出錯
- 你沿用之前的 build,而且過很久 artifacts 都消失了,需要重新 build
- 可能沒有 IAM 權限,試著在 buildspec, appspec 中打印 aws sts get-caller-identity
- 檢查權限的方式
- aws sts get-caller-identity
- curl http://169.254.169.254/latest/meta-data/iam/security-credentials/<相關 Role Name> 這段可以檢查目前的 Role 有沒有你指定的權限
References:
https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html
https://github.com/hashicorp/packer/issues/7142
沒有留言:
張貼留言