整體架構
- 使用 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
沒有留言:
張貼留言