Phần này nói về cách dùng công cụ Infrastructure as Code để tạo hạ tầng cho các tài khoản AWS khác nhau thuộc một AWS Organizations Banking, một vài câu hỏi quan trọng là: Ta dùng công cụ IaC gì? Cần tạo một hay nhiều Git Repository để chứa mã nguồn IaC? Tổ chức thư mục cho dự án như thế nào? Luồng để tạo hạ tầng ra sao?
Infrastructure as Code
Giải thích sơ về công cụ Infrastructure as Code là cách ta dùng mã nguồn để định nghĩa và tạo hạ tầng thay vì tạo thủ công trên AWS Console. Đối với một hạ tầng lớn nếu ta tạo hạ tầng thủ công bằng AWS Console thì rất khó để quản lý và sao chép ra các môi trường khác nhau. Trường hợp hạ tầng ngân hàng vừa lớn mà phải tạo trên nhiều tài khoản khác nhau, thì sử dụng IaC là việc chắc chắn cần thiết.
Công cụ IaC thì có rất nhiều như: Terraform, AWS CDK, Pulumi, … Bên mình sử dụng Terraform nên mình nói về cách dùng Terraform để tạo hạ tầng cho nhiều tài khoản AWS.
Dùng một hay nhiều Git Repository
Các tài khoản được đề cập ở bài AWS Account Management có tên như bên dưới:
- networking-nonprod
- workload-nonprod
- operation-nonprod
- observability-nonprod
- data-nonprod
- networking-prod
- workload-prod
- operation-prod
- observability-prod
- data-prod
Với tầm quan trọng của môi trường production thì mình gợi ý nên tạo Git Repository riêng biệt cho hai môi trường nonprod và prod, ví dụ tên như sau: nonprod-terraform, prod-terraform.
Tổ chức thư mục
Tiếp theo trong từng repository ta tạo thư mục theo tên tài khoản, nếu môi trường nonprod ta có nhiều môi trường như dev, uat, staging thì trong từng thư mục ứng với tên tài khoản ta tạo thêm thư mục ứng với tên môi trường, cấu trúc thư mục ví dụ:
└── nonprod-terraform
├── data-nonprod
│ ├── dev
│ └── uat
├── networking-nonprod
│ ├── dev
│ └── uat
├── observability-nonprod
│ ├── dev
│ └── uat
├── operation-nonprod
│ ├── dev
│ └── uat
└── workload-nonprod
├── dev
└── uat
Trong thư mục ứng với tên môi trường ta tạo thư mục cho từng thành phần của hạ tầng, ví dụ như vpc, eks, rds:
└── nonprod-terraform
├── data-nonprod
│ ├── dev
│ │ ├── eks
│ │ ├── rds
│ │ └── vpc
│ └── uat
│ ├── eks
│ ├── rds
│ └── vpc
├── networking-nonprod
│ ├── dev
│ └── uat
├── observability-nonprod
│ ├── dev
│ └── uat
├── operation-nonprod
│ ├── dev
│ └── uat
└── workload-nonprod
├── dev
└── uat
Khi Terraform tạo hạ tầng, nó tạo ra một tệp tin trạng thái để tham chiếu mã nguồn với hạ tầng thực tế. Tệp tin này thường được tạo ra trên máy tính mà bạn dùng để chạy Terraform. Nếu làm một mình thì không sao, nhưng khi nhiều người cùng sử dụng cùng một mã nguồn để tạo hạ tầng, thì bạn cần cơ chế để lưu tệp tin trạng thái ở nơi mà các thành viên trong nhóm khi chạy Terraform đều có thể truy cập được. Terraform có cơ chế gọi là Terraform Backend hỗ trợ lưu tệp tin trạng thái ở trên AWS S3 Bucket.
Như đã đề cập ở bài trước, tài khoản Operation được dùng để thực thi các tác vụ CI/CD và Terraform, trong tài khoản này, ta tạo một S3 Bucket để lưu tệp tin trạng thái. S3 Bucket này sẽ lưu trữ trạng thái hạ tầng của các tài khoản khác được tạo bằng Terraform. Ví dụ về cách khai báo Terraform Backend để tạo VPC ở tài khoản "data-nonprod" như sau:
terraform {
backend "s3" {
bucket = "nonprod-terraform-s3-backend"
key = "data-nonprod/dev/vpc"
region = "ap-southeast-1"
encrypt = true
dynamodb_table = "nonprod-terraform-s3-backend"
}
}
Ta lưu ý ở thuộc tính key, đây là thư mục ta lưu tệp tin trạng thái, nên đặt tên key tương ứng với tên thư mục của thành phần ta muốn tạo, ví dụ rds ở tài khoản data-nonprod.
terraform {
backend "s3" {
bucket = "nonprod-terraform-s3-backend"
key = "data-nonprod/dev/rds"
region = "ap-southeast-1"
encrypt = true
dynamodb_table = "nonprod-terraform-s3-backend"
}
}
Luồng để tạo hạ tầng
Để tạo hạ tầng, ta thực thi các tác vụ từ tài khoản Operation. Trong tài khoản Operation, ta tạo một IAM Role với các quyền truy cập vào S3, DynamoDB và KMS. Mục đích của IAM Role này là để lưu trữ tệp tin trạng thái hạ tầng của các tài khoản khác vào S3 Bucket.
Sau khi tạo IAM Role trong tài khoản Operation, ta sử dụng tính năng "Assume Role Cross Account" để lấy thông tin đăng nhập (Credentials) của tài khoản khác. Điều này cho phép ta thực thi các tác vụ tạo hạ tầng từ tài khoản Operation, nhưng sử dụng quyền truy cập của tài khoản khác.
Ví dụ, trong tài khoản Operation, ta tạo một IAM Role có tên là "nonprod-terraform-operation". Trước khi chạy Terraform, ta sử dụng user của tài khoản Operation thực thi câu lệnh sau để lấy token:
export JSON=$(aws sts assume-role --role-arn arn:aws:iam::0123456789:role/nonprod-terraform-operation --role-session-name "execution")
export AWS_ACCESS_KEY_ID=$(echo ${JSON} | jq --raw-output ".Credentials[\"AccessKeyId\"]")
export AWS_SECRET_ACCESS_KEY=$(echo ${JSON} | jq --raw-output ".Credentials[\"SecretAccessKey\"]")
export AWS_SESSION_TOKEN=$(echo ${JSON} | jq --raw-output ".Credentials[\"SessionToken\"]")
Token này có quyền truy cập vào S3 trong tài khoản Operation (mặc định Token tồn tại trong vòng 15 phút). Bên cạnh đó, Token này còn định danh cho một user tạm thời có quyền Assume Role ở các tài khoản khác. Ví dụ, với câu lệnh CLI trên, user tạm thời được tạo ra có tên là "execution". Tên này được chỉ định thông qua tham số --role-session-name
trong câu lệnh aws sts assume-role
. Đầy đủ ARN (Amazon Resource Name) như sau:
arn:aws:sts::0123456789:assumed-role/nonprod-terraform-operation/execution
Tiếp theo, ở các tài khoản khác, ta tạo một IAM Role với các quyền cần thiết để tạo hạ tầng trên đó. Lưu ý rằng chỉ nên cấp quyền vừa đủ và tuân thủ Security best practices in IAM của AWS. Ví dụ, trong tài khoản "data-nonprod", ta tạo một IAM Role có tên là "nonprod-terraform-operation". Sau đó, ta cho phép arn:aws:sts::0123456789:assumed-role/nonprod-terraform-operation/execution
có quyền Assume Role.
Cuối cùng ở Terraform ta khai báo AWS Provider như sau, thư mục data-nonpord/dev/vpc:
terraform {
backend "s3" {
bucket = "nonprod-terraform-s3-backend"
key = "data-nonprod/dev/vpc"
region = "ap-southeast-1"
encrypt = true
dynamodb_table = "nonprod-terraform-s3-backend"
}
}
provider "aws" {
assume_role {
role_arn = "arn:aws:iam::<data-nonprod-account-id>:role/nonprod-terraform-operation"
}
}
Tương tự cho các tài khoản khác, ví dụ networking-nonprod, thư mục networking-nonprod/uat/rds:
terraform {
backend "s3" {
bucket = "nonprod-terraform-s3-backend"
key = "networking-nonprod/uat/rds"
region = "ap-southeast-1"
encrypt = true
dynamodb_table = "nonprod-terraform-s3-backend"
}
}
provider "aws" {
assume_role {
role_arn = "arn:aws:iam::<networking-nonprod-account-id>:role/nonprod-terraform-operation"
}
}
Để thuận tiện, những bước lấy Token có thể được tự động hóa thông qua CI/CD. Khi chạy CI/CD, ta cần chạy một script để phát hiện thư mục nào đã thay đổi, sau đó nhảy vào trong thư mục đó để chạy Terraform. Ví dụ, khi sử dụng GitHub Actions, ta có thể dùng action tj-actions/changed-files để xác định các thư mục đã thay đổi.
Bài tiếp theo mình sẽ nói về Networking for Multi AWS Accounts.
Điểm hẹn ngân hàng số Vikki by HDBank
Loạt bài viết ngắn chia sẻ về kiến trúc hạ tầng của hệ thống ngân hàng trên Cloud (AWS). Đây chỉ là những kiến thức mình học được thông qua sản phẩm NGÂN HÀNG SỐ VIKKI của bên mình, nên kiến trúc có thể phù hợp và không phù hợp với doanh nghiệp của các bạn.
- AWS Account Management
- Provisioning Infrastructure for Multi AWS Accounts
- Networking for Multi AWS Accounts
- Kubernetes for Multi AWS Accounts: Kubernetes Infrastructure for Scale
- Kubernetes for Multi AWS Accounts: Kubernetes Cross Cluster Communication
- Chaos Engineering
- On-call
- Core Banking on Cloud
- Security Consider