1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Terraform、k8s、Helm のハンズオンまとめ

Last updated at Posted at 2024-12-04

はじめに

社内研修でTerraform、k8s、Helmを使用したGoogle Cloud環境の構築を行ったので、その学習内容のまとめです。

ハンズオンで構築したもの

  • Google CloudでGKE Autopilotクラスタを構築
  • Wordpress(バックエンドにCloud SQL)、ArgoCDをGKE上にデプロイ
  • 秘匿情報はExternal Secret Operatorを使用する
  • Cloud SQLの起動停止ジョブ

Terraformを使用してGKEクラスタを作成する

Google Cloud上のリソースを一つずつ把握しながら作成していきたかったので、あえてモジュールを使用せずにリソースで実装しました。

実装例
variables.tf
variable "project_id" {
  description = "The project ID to host the cluster in"
  default     = "test-xxxxx-xxxxx"
}

variable "gke_name" {
  default     = "test"
}

variable "region" {
  description = "The region the cluster in"
  default     = "asia-northeast1"
}
provider.tf
provider "google" {
  project = var.project_id  
}
cluster.tf
resource "google_compute_network" "default" {
  name = "${var.gke_name}-network"
  project = var.project_id

  auto_create_subnetworks  = false
  enable_ula_internal_ipv6 = true
}

resource "google_compute_subnetwork" "default" {
  name = "${var.gke_name}-subnetwork"
  project = var.project_id

  ip_cidr_range = "10.0.0.0/16"
  region        = var.region

  stack_type       = "IPV4_IPV6"
  ipv6_access_type = "EXTERNAL" # Change to "EXTERNAL" if creating an external loadbalancer

  network = google_compute_network.default.id
  secondary_ip_range {
    range_name    = "services-range"
    ip_cidr_range = "192.168.0.0/24"
  }

  secondary_ip_range {
    range_name    = "pod-ranges"
    ip_cidr_range = "192.168.1.0/24"
  }
}

resource "google_container_cluster" "default" {
  name = "${var.gke_name}-autopilot-cluster"
  project = var.project_id

  location                 = var.region
  enable_autopilot         = true
  enable_l4_ilb_subsetting = true

  network    = google_compute_network.default.id
  subnetwork = google_compute_subnetwork.default.id

  resource_labels = {
    team = "research"
  }

  ip_allocation_policy {
    stack_type                    = "IPV4_IPV6"
    services_secondary_range_name = google_compute_subnetwork.default.secondary_ip_range[0].range_name
    cluster_secondary_range_name  = google_compute_subnetwork.default.secondary_ip_range[1].range_name
  }  

  # Set `deletion_protection` to `true` will ensure that one cannot
  # accidentally delete this instance by use of Terraform.
  deletion_protection = false
}

内部VPNのみ接続可能な設定とする

公開クラスタでクラスタを作成し、Cloud Armorで全部のアクセスを拒否するデフォルトルールと内部VPNのみ許可するセキュリティポリシーを追加し、IngressとBackendConfigによる制御を実装しました。

実装例
backendconfig.yaml
apiVersion: cloud.google.com/v1
kind: BackendConfig
metadata:
  name: hello-backend-config
  namespace: default
spec:
  securityPolicy:
    name: hello-sec-policy  # 作成したセキュリティポリシーの名前
ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress
  namespace: default
  annotations:
    kubernetes.io/ingress.class: "gce"  # 外部 HTTP(S) ロードバランサを指定
spec:
  defaultBackend:
    service:
      name: hello-app
      port:
        number: 8080
hello-app.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-app
  namespace: default
spec:
  selector:
    matchLabels:
      run: hello-app
  template:
    metadata:
      labels:
        run: hello-app
    spec:
      containers:
      - image: us-docker.pkg.dev/google-samples/containers/gke/hello-app:1.0
        imagePullPolicy: IfNotPresent
        name: hello-app
        ports:
        - containerPort: 8080
          protocol: TCP
security.tf
resource "google_compute_security_policy" "policy" {
  name = "hello-sec-policy"
  project = var.project_id

  rule {
    action   = "deny(403)"
    priority = "2147483647"
    match {
      versioned_expr = "SRC_IPS_V1"
      config {
        src_ip_ranges = ["*"]
      }
    }
    description = "default rule"
  }

  rule {
    action   = "allow"
    priority = "1000"
    match {
      versioned_expr = "SRC_IPS_V1"
      config {
        src_ip_ranges = ["XXX.XX.XX.XX/32"]
      }
    }
  }
}

tfstateファイルの実装

今回はGoogle Cloud Storageを使用して、tfstateファイルを管理するためバージョン管理と暗号化、ロック機能はCloud Storageの設定でできました。
AWSの場合は、DynamoDBを使用して排他ロックをかける必要があるそうです。

tfstateファイルの実装
main.tf
resource "google_storage_bucket" "default" {
  name     = "terraform-remote-backend"
  location = "ASIA-NORTHEAST1"
  project = var.project_id

  force_destroy               = false
  public_access_prevention    = "enforced"
  uniform_bucket_level_access = true

  versioning {
    enabled = true
  }
}
backend.tf
terraform {
  backend "gcs" {
    bucket = "terraform-remote-backend"
  }
}

Google Cloudのクラスタについて

GKEにはAutopilot モードと Standard モードが選択でき、今回はフルマネージドサービスであるAutopilotを採用しました。セキュリティはデフォルトで有効となっていて、自動スケーリングの様子も確認することができました。また、Workload Identityもデフォルトで有効化されています。

Autopilotでは、デプロイしたリソースを削除すると自動的にノードがゼロまで削減されます。また、request/limitを指定しないとデフォルトのCPU、memory、エフェメラルストレージが設定されます。
デフォルトはCPUが0.5vCPU、memoryが2GiB、エフェメラルストレージが1GiBとなっていました。今回作成した、Wordpress、External Secrets Operator、ArgoCD、Ingress、BackEndConfigをリリースしただけで
4〜5ノードが起動してコストがかかるため、それぞれに最低限のリソースを使用するように設定しておいた方がよさそうでした。
最小設定は、CPUが50m メモリは52MiBとなっています。

Helm Chartを使用してGKE上にWordpressを構築する

Helm Chartについて

下記のHelm Chartを使用して、values.yamlで設定可能な値を変更しました。
https://artifacthub.io/packages/helm/bitnami/wordpress

変更点は下記です。

  • エフェメラルストレージが不足しているというエラーが出たので、エフェメラルストレージの指定を変更しました
  • WordpressにIP制限をかけるため、アノテーションを追加しました
  • 機密情報(DBのパスワード、Wordpressのパスワード)はExternal Secret Operatorを使用したので、ExternalSecretで指定したk8s Secret名に変更しました
  • DBはMariaDBからCloud SQL(MySQL)に変更しました
values.yaml
wordpress/values.yaml
existingSecret: "wordpress-credentials"
resources:
  requests:
    ephemeral-storage: 300Mi
    cpu: 100m
    memory: 128Mi
  limits:
    ephemeral-storage: 300Mi
    cpu: 200m
    memory: 256Mi
service:
  type: NodePort
  annotations: {
    cloud.google.com/backend-config: '{"ports": {"80":"hello-backend-config"}}' 
    ,cloud.google.com/neg: '{"ingress": true}'
  }
mariadb:
  enabled: false
externalDatabase:
  host: "xxxxxxxxxxxx.xxxxxxxxxxxx.asia-northeast1.sql.goog."
  user: "wordpress-admin-user"
  database: "wordpress-db"
  existingSecret: "wordpress-credentials"

WordpressのDBをMariaDBからCloud SQL(MySQL)に変更する

Terrafromを使用してCloud SQLを作成しました。DBユーザーの作成は機密情報のため、コンソールから行いました。
インスタンス接続はPrivate Service Connectを使用しました。

Cloud SQL
db.tf
# Enable APIs
locals {
  services = toset([
    "sqladmin.googleapis.com",
    "servicenetworking.googleapis.com",
    "networkmanagement.googleapis.com"
  ])
}

resource "google_project_service" "db" {
  for_each = local.services
  service  = each.value
  disable_on_destroy = false
}

# Cloud SQLインスタンスの作成
resource "google_sql_database_instance" "default" {
  name             = "mysql-instance"
  region           = var.region
  database_version = "MYSQL_8_0"
  settings {
    tier              = "db-f1-micro"
    availability_type = "ZONAL" # 高可用性構成 (REGIONAL) or single zone (ZONAL)
    disk_autoresize = false     # ストレージの自動増量

    backup_configuration {
      enabled            = false # 自動バックアップ
      binary_log_enabled = false
    }
    ip_configuration {
      psc_config {
        psc_enabled               = true
        allowed_consumer_projects = [var.project_id]
      }
      ipv4_enabled = false
    }
  }
  deletion_protection = false # Set to "true" to prevent destruction of the resource
}

# プライベート IP アドレスを予約
resource "google_compute_address" "default" {
  name         = "psc-compute-address-${google_sql_database_instance.default.name}"
  region       = var.region
  address_type = "INTERNAL"
  subnetwork   = google_compute_subnetwork.default.id
  address      = "10.0.0.10"
}

data "google_sql_database_instance" "default" {
  name = resource.google_sql_database_instance.default.name
}

# Private Service Connect エンドポイントを作成する
resource "google_compute_forwarding_rule" "default" {
  name                  = "psc-forwarding-rule-${google_sql_database_instance.default.name}"
  region                = var.region
  network               = google_compute_network.default.id #VPC名
  ip_address            = google_compute_address.default.self_link 
  load_balancing_scheme = ""
  target                = data.google_sql_database_instance.default.psc_service_attachment_link
}

# databaseの作成
resource "google_sql_database" "database" {
  name     = "wordpress-db"
  instance = google_sql_database_instance.default.name
}

# DBユーザーの作成の作成はコンソールから行う

# Cloud SQL Auth Proxyを使用して接続する
# 限定公開 DNS ゾーンを作成する
resource "google_dns_managed_zone" "default" {
  name     = "cloud-sql-dns"
  description = "Cloud SQL インスタンスの DNS ゾーン"
  dns_name = data.google_sql_database_instance.default.dns_name
  visibility = "private"
  cloud_logging_config {
    enable_logging = false
  }
  private_visibility_config {
    networks {
      network_url = google_compute_network.default.id
    }
  }
}

# ゾーンに DNS レコードを作成する
resource "google_dns_record_set" "a" {
  name         = google_dns_managed_zone.default.dns_name
  managed_zone = google_dns_managed_zone.default.name
  type         = "A"
  ttl          = 0

  rrdatas = [google_compute_address.default.address]
}

External Secrets Operatorの導入

秘匿情報の取り扱いのためGoogle Cloud Secret ManagerとExternal Secrets Operatorを使用しました。Google Cloud Secret Managerで秘匿情報を管理し、それをExternal Secrets Operatorを使用してGKE上のKubernetes Secretsとして同期しました。

HelmでExternal Secrets Operatorをインストールした後、ClusterSecretStoreとExternalSecretをapplyする必要があります。

helm repo add external-secrets https://charts.external-secrets.io

helm install external-secrets \
   external-secrets/external-secrets \
    -n external-secrets \
    --create-namespace \
External Secrets Operatorの実装
cluster-secret-store.yaml
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: gcp-store
  namespace: external-secrets
spec:
  provider:
    gcpsm:
      projectID: test-xxxxx-xxxxx
      auth:
        workloadIdentity:
          # name of the cluster Location, region or zone
          clusterLocation: asia-northeast1
          # name of the GKE cluster
          clusterName: test-autopilot-cluster
          # projectID of the cluster (if omitted defaults to spec.provider.gcpsm.projectID)
          clusterProjectID: test-xxxxx-xxxxx
          # reference the sa from above
          serviceAccountRef:
            name: external-secrets
            namespace: external-secrets
external-secret.yaml

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: wordpress-credentials
spec:
  refreshInterval: 24h             # rate SecretManager pulls GCPSM
  secretStoreRef:
    kind: ClusterSecretStore
    name: gcp-store               # name of the SecretStore (or kind specified)
  target:
    name: wordpress-credentials    # name of the k8s Secret to be created
    creationPolicy: Owner
  data:
  - secretKey: wordpress-password
    remoteRef:
      key: wordpress-pass      # name of the GCPSM secret key
  - secretKey: mariadb-password
    remoteRef:
      key: db-pass      # name of the GCPSM secret key

今後の展望

CloudSQLの夜間インスタンス停止の実装とArgoCDの実装について続きを書ければと思います。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?