Overview
작년에 진행했었던 AWS에서 GCP로 마이그레이션 여정에 대해서 정리해보려고 한다.
1. AWS ↔ GCP 리소스 정리
2023.05.05 - [GCP] - GCP vs AWS 리소스 비교
기존 AWS 리소스와 GCP 리소스를 간단하게 정리하였다.
Network
- Virtual Private Cloud = AWS VPC
- Cloud Firewall = AWS SG, NetworkACL
- Google Cloud Armor = AWS WAF
- Cloud NAT = AWS NAT Gateway
- Cloud Load Balancing = AWS CLB, ELB, ALB
- Cloud CDN = AWS CloudFront
- Static External IP = AWS Elastic IP
Compute
- Compute Engine = AWS EC2
Containers
- Google Kubernetes Engin(GKE) = AWS EKS
- GCP GKE Autopilot mode = AWS EKS Fargate (?)
- Container Registry & Artifact Registry = AWS ECR
Storage
- Cloud Storage = AWS S3
- Filestore Instance = AWS EFS
Database
- Memorystore = AWS ElasticCache
- Cloud SQL = AWS RDS & Amazon Aurora
- Cloud Spanner = Amazon Aurora
Security & Identity
- Secret Manager = AWS Secrets Manager
- IAM = AWS IAM
- Certificate Authority Service = AWS Certificate Manager(ACM)
2. GCP 공부(IAM, 구조, SDK..)
GCP에서 대해서 공부를 시작하였다. GCP의 IAM을 공부하며 조직-폴더-프로젝트에 구조 그리고 gcloud SDK 설정도 하였다.
Shared VPC와 Workload Identity Federation이라는 중요한 개념에 대해서도 알게되었다. 해당 개념을 바탕으로 Host Project와 Service Project를 구분하여 Shared VPC를 구성하였다. 그리고 CICD를 Github Action과 ArgoCD를 사용중인데, Github Action에서 서비스 어카운트를 사용하지 않고 Workload Identity Federation을 통해 임시 토큰을 생성해서 CICD를 구성할 수 있게되었다.
2023.04.06 - [GCP] - GCP란? - 서비스 계정 & Project 생성 / SDK(gcloud) 설치
2023.04.12 - [GCP] - GCP - SDK(gcloud) 계정 2개 등록하기
2023.04.06 - [GCP] - GCP IAM이란?
Terraform으로 간단하게 Workload Identity Federation 생성하는 방법이다.
## Service Account ##
module "service_accounts" {
source = "../../modules/service_accounts"
project_id = var.project
names = ["github-action"]
display_name = "github-action"
description = "github-action admin"
}
## Workload Identity Federation ##
data "google_service_account" "github-action" {
account_id = "github-action"
depends_on = [module.service_accounts]
}
module "workload_identity_federation" {
source = "../../modules/workload_identity_federation"
project_id = var.project
pool_id = "pool-github-action"
provider_id = "provider-github-action"
attribute_mapping = {
"google.subject" = "assertion.sub"
"attribute.actor" = "assertion.actor"
"attribute.aud" = "assertion.aud"
"attribute.repository" = "assertion.repository"
}
issuer_uri = "https://token.actions.githubusercontent.com"
service_accounts = [
{
name = data.google_service_account.github-action.name
attribute = "attribute.repository/somaz94/*"
all_identities = true
},
{
name = data.google_service_account.github-action.name
attribute = "attribute.repository/somaz94-ai/*"
all_identities = true
}
]
}
3. AWS ↔ GCP DNS Migration 준비
AWS Route53에서 사용하고 있는 DNS를 전부 정리하였고, GCP에 동일하게 생성하였다. 그리고 AWS에서 서브도메인을 생성해 GCP에 생성한 서브도메인 Zone의 NS를 바라보게 설정해서 위임하였다.
Hosting.kr → AWS Route53 → GCP Cloud DNS(서브 도메인)
서브도메인을 생성한 이유는 모든 DNS를 Migration하려면 결국 Production을 옮길때 전부다 옮겨줄 수 있기 때문이다.
최종적으로는 AWS Route53을 바라보고 있는 도메인을 GCP Cloud DNS를 바라보게 설정해줘야 한다.
Hosting.kr → AWS Route53 / Hosting.kr → GCP Cloud DNS
4. Terraform 공부 및 리소스 생성
GCP에선 Terraform을 사용해서 모든 리소스를 관리하려고 하였다. 따라서 Terraform 공부가 필요했고 여러개의 기술 블로그와 Terraform Docs, Terraform Github Module을 보며 공부하였다.
- https://btcd.tistory.com/156 - VPC
- https://nangman14.tistory.com/84 - 모듈설명
- https://dev.classmethod.jp/articles/terraform-bset-practice-kr/ - 모범사례
- https://linuxer.name/2020/01/gcp-terrafrom-1-with-google-cloud-shell/ - 리눅서님 블로그
- https://developer.hashicorp.com/terraform/docs - Terraform Docs
- https://github.com/terraform-google-modules - Terraform Google Modules
- https://registry.terraform.io/providers/hashicorp/google/latest/docs Terraform Provider Docs
그리고 파일구조에 대해서 고민하였다. 처음에는 리소스별로 파일구조를 구성하였지만, 너무 복잡해지고 디렉토리가 너무 많아졌다. 따라서, 고민한 결과 아래의 구조가 완성되었다.
.
├── LICENSE
├── README.md
├── key
├── modules
│ ├── cloud_armor
│ ├── gcs_buckets
│ ├── gke_autopilot
│ ├── memorystore
│ ├── mysql
│ ├── network
│ │ ├── README.md
│ │ ├── main.tf
│ │ ├── metadata.yaml
│ │ ├── modules
│ │ │ ├── fabric-net-svpc-access
│ │ │ ├── firewall-rules
│ │ │ ├── network-firewall-policy
│ │ │ ├── private-service-connect
│ │ │ ├── routes
│ │ │ ├── routes-beta
│ │ │ ├── subnets
│ │ │ ├── subnets-beta
│ │ │ ├── vpc
│ │ │ └── vpc-serverless-connector-beta
│ │ ├── outputs.tf
│ │ ├── variables.tf
│ │ └── versions.tf
│ ├── network_peering
│ ├── postgresql
│ ├── private_service_access
│ ├── secret_manager
│ ├── service_accounts
│ └── workload_identity_federation
└── project
├── somaz-ai-project
├── somaz-bigquery-project
├── somaz-host-project
└── somaz-service-project
├── dev
├── prod
└── qa
https://github.com/somaz94/terraform-infra-gcp
리소스를 구성하면서 특이사항이 있었는데 AWS EFS와 유사한 기능을 가지고 있는 Firestore Instance를 구성하였지만, SSD 최소 크기가 2.5TB라서 비용문제가 있기 때문에 사용하지 못하게 되었다.
https://cloud.google.com/filestore/docs/creating-instances?hl=ko#allocate_capacity
따라서 Compute Engine을 만들어 NFS Server를 아래와 같이 구성하였다.
## Compute Engine ##
resource "google_compute_address" "nfs_server_ip" {
name = var.nfs_server_ip
}
resource "google_compute_disk" "additional_pd_balanced" {
name = "nfs-disk"
type = "pd-balanced"
zone = "${var.region}-a"
size = 1000
}
resource "google_compute_instance" "nfs_server" {
name = var.nfs_server
machine_type = "e2-standard-2"
labels = local.default_labels
zone = "${var.region}-a"
allow_stopping_for_update = true
tags = [var.nfs_server]
boot_disk {
initialize_params {
image = "ubuntu-os-cloud/ubuntu-2004-lts"
}
}
attached_disk {
source = google_compute_disk.additional_pd_balanced.self_link
device_name = google_compute_disk.additional_pd_balanced.name
}
metadata = {
ssh-keys = "somaz:${file("../../key/somaz-nfs-server.pub")}" # Change somaz (your compute engine user)
}
metadata_startup_script = <<-EOF
#!/bin/bash
sudo apt-get update
sudo apt-get install -y nfs-kernel-server
sudo mkdir /nfs
sudo mkfs.xfs /dev/sdb
echo -e '/dev/sdb /nfs xfs defaults,nofail 0 0' | sudo tee -a /etc/fstab
sudo mount -a
EOF
network_interface {
network = var.shared_vpc
subnetwork = "${var.subnet_share}-mgmt-a"
access_config {
## Include this section to give the VM an external ip ##
nat_ip = google_compute_address.nfs_server_ip.address
}
}
depends_on = [module.vpc, google_compute_address.nfs_server_ip, google_compute_disk.additional_pd_balanced]
}
5. CICD 구성(Github Action + ArgoCD)
Github Action과 ArgoCD를 사용해서 CICD를 구성하였다. Github Action은 모듈이 잘되어 있어서 크게 어렵지 않았다.
ArgoCD Docs를 읽으면서 구성하였다.
https://github.com/somaz94/cicd-monitoring/tree/main/github-cicd
2023.08.09 - [IaC/CI CD Tool] - ArgoCD 설치 AWS & GCP
https://github.com/somaz94/helm-chart-template/tree/main/gcp
6. Monitoring 구성
현재 모니터링은 PLG 스택을 사용중이다. PLG 스택은 Prometheus, Loki, Grafana이다. 그리고 Pod마다 Promtail을 SideCar로 붙여서 Loki로 수집하고 Grafana로 시각화 해주고 있다.
2023.02.12 - [교육, 커뮤니티 후기/PKOS 쿠버네티스 스터디] - PKOS 쿠버네티스 스터디 5주차 - 프로메테우스 그라파나
https://github.com/somaz94/cicd-monitoring/tree/main/gcp
7. 마이그레이션 중 어려웠거나 특이사항
- GCP 전체구조를 이해하는게 생각보다 오래걸렸다. 조직, 폴더, 프로젝트별로 전부 권한을 줄수가 있었고 심지어 프로젝트가 다르면 A 프로젝트의 API의 권한을 B 프로젝트의 서비스 어카운트 또는 API 어카운트에게 부여해야 했다. 따라서 실행할때마다 권한문제가 자주 발생하였고, 하나씩 확인해가면서 진행하였다.
- Terraform을 처음 사용해보았기 때문에 Reference를 많이 찾아보았지만 생각보다 Terraform 구조에 대한 설명이 나와있는 Reference가 많지 않았다. 따라서 최대한 참고하여 나만의 구조를 만들었고, tf 파일은 Resource 별로 분류하였다. 그리고 Terraform Registry Docs와 Terraform Module이 도움이 많이 되었다. Module과 Resource를 적절하게 섞어서 구성하였다.
README.md cloud-nat.tf firewall.tf locals.tf organization-custom-iam.tf provider.tf vpc.tf artifact-registry.tf cloud-storage.tf gitlab.tf mgmt.tfvars outputs.tf terraform-backend.tf workload-identity-federation.tf cloud-dns-lb-ip.tf compute-engine.tf kubernetes-engine.tf mongo-log-blockchain-lb.tf private-service-access.tf variables.tf
- GKE를 사용해서 Ingress를 구성할때, backendconfig와 frontendconfig, managedcertificate와 같이 GKE에서만 사용하는 리소스들이 있었다. 그리고 AWS와 다르게 GKE LoadBalancer BackendConfig에 정의된 healthCheck 경로의 requestPath에서 응답을 받지 못하면 Server Error(503)이 발생하였다. health check가 성공해야 외부에서 확인이 가능하다. 그리고 DNS 생성시 static lb ip(ex. somaz-gke-lb-ip)를 생성해주는 것이 좋다.
apiVersion: networking.gke.io/v1 kind: ManagedCertificate metadata: name: argocd-certificate spec: domains: - argocd.somaz.link # Modified your Domain --- apiVersion: cloud.google.com/v1 kind: BackendConfig metadata: name: argocd-backend-config namespace: argocd spec: healthCheck: checkIntervalSec: 30 timeoutSec: 5 healthyThreshold: 1 unhealthyThreshold: 2 type: HTTP requestPath: /healthz port: 8080 --- apiVersion: networking.gke.io/v1beta1 kind: FrontendConfig metadata: name: argocd-frontend-config namespace: argocd spec: redirectToHttps: enabled: true --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: argocd-server-ingress namespace: argocd annotations: kubernetes.io/ingress.global-static-ip-name: "somaz-gke-argocd-lb-ip" # Modified global-static-ip-name networking.gke.io/managed-certificates: "argocd-certificate" kubernetes.io/ingress.class: "gce" networking.gke.io/v1beta1.FrontendConfig: argocd-frontend-config spec: rules: - host: argocd.somaz.link # Modified Domain http: paths: - pathType: Prefix path: "/" backend: service: name: argocd-server port: number: 80
- 다행히 게임을 서비스하고 있어서 점검을 걸고 실시간 마이그레이션을 진행하지 않아도 되었다. 그리고 DNS까지 전체 마이그레이션을 진행해야 했기 때문에 기존 DNS를 사용하려면 실시간 마이그레이션은 불가능하였다. 모든 리소스와 CICD Monitoring 구성을 완료한뒤에, DNS만 변경해주면서 마이그레이션을 진행하였다.
- 마이그레이션 완료후에 CDN에 http → https redirect 설정을 해주지 않아서 게임 클라이언트 파일 다운로드 에러가 발생하였다. 다행히 금방 원인을 찾을 수 있었고 아래와 같이 수정하여 해결하였다.
## CDN(somaz_link) ## resource "google_compute_managed_ssl_certificate" "cdn_lb_certificate" { name = "somaz-link-ssl-cert" managed { domains = [var.somaz_link] } } resource "google_compute_backend_bucket" "somaz_link_bucket_backend" { name = "somaz-link-backend" bucket_name = var.somaz_link enable_cdn = true edge_security_policy = module.cloud_armor_region_block.policy_self_link compression_mode = "AUTOMATIC" custom_response_headers = [ "Access-Control-Allow-Origin: *", "Access-Control-Allow-Methods: GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE", "Access-Control-Allow-Headers: *", "Strict-Transport-Security: max-age=31536000", "X-Content-Type-Options: nosniff", "X-XSS-Protection: 1; mode=block", "Referrer-Policy: strict-origin-when-cross-origin" ] depends_on = [google_storage_bucket.somaz_link, module.cloud_armor_ip_allow] } resource "google_compute_url_map" "somaz_link_http_url_map" { name = "somaz-link-http-url-map" default_url_redirect { https_redirect = true strip_query = false } depends_on = [google_storage_bucket.somaz_link, google_compute_backend_bucket.somaz_link_bucket_backend] } resource "google_compute_url_map" "somaz_link_https_url_map" { name = "somaz-link-https-url-map" default_service = google_compute_backend_bucket.somaz_link_bucket_backend.id depends_on = [google_storage_bucket.somaz_link, google_compute_backend_bucket.somaz_link_bucket_backend] } resource "google_compute_target_http_proxy" "somaz_link_http_proxy" { name = "somaz-link-http-proxy" url_map = google_compute_url_map.somaz_link_http_url_map.self_link } resource "google_compute_global_forwarding_rule" "somaz_link_http_forwarding_rule" { name = "somaz-link-http-forwarding-rule" target = google_compute_target_http_proxy.somaz_link_http_proxy.self_link port_range = "80" ip_address = google_compute_global_address.somaz_link_lb_ip.address } resource "google_compute_target_https_proxy" "somaz_link_https_proxy" { name = "somaz-link-https-proxy" url_map = google_compute_url_map.somaz_link_https_url_map.self_link ssl_certificates = [google_compute_managed_ssl_certificate.cdn_lb_certificate.self_link] } resource "google_compute_global_forwarding_rule" "somaz_link_https_forwarding_rule" { name = "somaz-link-https-forwarding-rule" target = google_compute_target_https_proxy.somaz_link_https_proxy.self_link port_range = "443" ip_address = google_compute_global_address.somaz_link_lb_ip.address }
- Helm chart template을 Customize하여 새롭게 작성하는 방법도 필요하였다. 생각보다 오랜시간이 걸렸고, yaml과 helm에 대한 이해도를 높일 수 있었다.
- 마지막으로 AWS 전체 인프라를 GCP로 마이그레이션 하면서 인프라와 다양한 Tool에 대한 이해도가 높아졌다. 상당히 좋은 경험이 되었고 한걸음? 성장하는 계기가 되었던 것 같다.
Reference
- https://btcd.tistory.com/156 - VPC
- https://nangman14.tistory.com/84 - 모듈설명
- https://dev.classmethod.jp/articles/terraform-bset-practice-kr/ - 모범사례
- https://linuxer.name/2020/01/gcp-terrafrom-1-with-google-cloud-shell/ - 리눅서님 블로그
- https://developer.hashicorp.com/terraform/docs - Terraform Docs
- https://github.com/terraform-google-modules - Terraform Google Modules
- https://registry.terraform.io/providers/hashicorp/google/latest/docs Terraform Provider Docs