Complete CI/CD with Kubernetes/Docker, Terraform and GKE(Google Kubernetes Engine)
Table of contents
Tech used:
Node.js
Docker
Kubernetes
Terraform
GitHub Actions
GKE(Google Kubernetes Engine)
GCR(Google Container Registry)
Steps
Create a simple nodejs/express application.
Write Dockerfile for the application
FROM --platform=linux/amd64 node:14 WORKDIR /usr/app COPY package.json . RUN npm install COPY . . EXPOSE 80 CMD ["node","app.js"]
Write Terraform scripts for GKE Cluster, Deployment and service.
providers.tf
: use google and kubernetes providersterraform { required_version = ">= 0.12" backend "gcs" { } } provider "google" { project = var.project_id region = var.region } provider "kubernetes" { host = google_container_cluster.default.endpoint token = data.google_client_config.current.access_token client_certificate = base64decode( google_container_cluster.default.master_auth[0].client_certificate, ) client_key = base64decode(google_container_cluster.default.master_auth[0].client_key) cluster_ca_certificate = base64decode( google_container_cluster.default.master_auth[0].cluster_ca_certificate, ) }
main.tf
: for creating GKE Clusterdata "google_container_engine_versions" "default" { location = "us-central1-c" } data "google_client_config" "current" { } resource "google_container_cluster" "default" { name = "my-first-cluster" location = "us-central1-c" initial_node_count = 3 min_master_version = data.google_container_engine_versions.default.latest_master_version node_config { machine_type = "g1-small" disk_size_gb = 32 } provisioner "local-exec" { when = destroy command = "sleep 90" } }
k8s.tf
: For deployment and service deployment on K8sresource "kubernetes_deployment" "name" { metadata { name = "nodeappdeployment" labels = { "type" = "backend" "app" = "nodeapp" } } spec { replicas = 1 selector { match_labels = { "type" = "backend" "app" = "nodeapp" } } template { metadata { name = "nodeapppod" labels = { "type" = "backend" "app" = "nodeapp" } } spec { container { name = "nodecontainer" image = var.container_image port { container_port = 80 } } } } } } resource "google_compute_address" "default" { name = "ipforservice" region = var.region } resource "kubernetes_service" "appservice" { metadata { name = "nodeapp-lb-service" } spec { type = "LoadBalancer" load_balancer_ip = google_compute_address.default.address port { port = 80 target_port = 80 } selector = { "type" = "backend" "app" = "nodeapp" } } }
-
variable "region" { } variable "project_id" { } variable "container_image" { }
-
output "cluster_name" { value = google_container_cluster.default.name } output "cluster_endpoint" { value = google_container_cluster.default.endpoint } output "cluster_location" { value = google_container_cluster.default.location } output "load-balancer-ip" { value = google_compute_address.default.address }
Setup Github OIDC Authentication with GCP
Create a new workload Identity pool
gcloud iam workload-identity-pools create "k8s-pool" \ --project="${PROJECT_ID}" \ --location="global" \ --display-name="k8s Pool"
Create a oidc identity provider for authenticating with Github
gcloud iam workload-identity-pools providers create-oidc "k8s-provider" \ --project="${PROJECT_ID}" \ --location="global" \ --workload-identity-pool="k8s-pool" \ --display-name="k8s provider" \ --attribute-mapping="google.subject=assertion.sub,attribute.actor=assertion.actor,attribute.aud=assertion.aud" \ --issuer-uri="https://token.actions.githubusercontent.com"
Create a service account with these permissions
roles/compute.admin roles/container.admin roles/container.clusterAdmin roles/iam.serviceAccountTokenCreator roles/iam.serviceAccountUser roles/storage.admin
Add IAM Policy bindings with Github repo, Identity provider and service account.
gcloud iam service-accounts add-iam-policy-binding "${SERVICE_ACCOUNT_EMAIL}" \ --project="${GCP_PROJECT_ID}" \ --role="roles/iam.workloadIdentityUser" \ --member="principalSet://iam.googleapis.com/projects/${GCP_PROJECT_NUMBER}/locations/global/workloadIdentityPools/k8s-pool/attribute.repository/${GITHUB_REPO}"
Create a bucket in GCS for storing terraform state file.
Get your GCP Project number for reference.
gcloud projects describe ${PROJECT_ID}
Add secrets to Github Repo
GCP_PROJECT_ID
GCP_TF_STATE_BUCKET
- write GH Actions workflow for deploying our app to GKE using terraform
name: Deploy to kubernetes
on:
push:
branches:
- "Complete-CI/CD-with-Terraform-GKE"
env:
GCP_PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }}
TF_STATE_BUCKET_NAME: ${{ secrets.GCP_TF_STATE_BUCKET }}
jobs:
deploy:
runs-on: ubuntu-latest
env:
IMAGE_TAG: ${{ github.sha }}
permissions:
contents: 'read'
id-token: 'write'
steps:
- uses: 'actions/checkout@v3'
- id: 'auth'
name: 'Authenticate to Google Cloud'
uses: 'google-github-actions/auth@v1'
with:
token_format: 'access_token'
workload_identity_provider: 'projects/886257991781/locations/global/workloadIdentityPools/k8s-pool/providers/k8s-provider'
service_account: 'tf-gke-test@$GCP_PROJECT_ID.iam.gserviceaccount.com'
- name: 'Set up Cloud SDK'
uses: 'google-github-actions/setup-gcloud@v1'
- name: docker auth
run: gcloud auth configure-docker
- run: gcloud auth list
- name: Build and push docker image
run: |
docker build -t us.gcr.io/$GCP_PROJECT_ID/nodeappimage:$IMAGE_TAG .
docker push us.gcr.io/$GCP_PROJECT_ID/nodeappimage:$IMAGE_TAG
working-directory: ./nodeapp
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
- name: Terraform init
run: terraform init -backend-config="bucket=$TF_STATE_BUCKET_NAME" -backend-config="prefix=test"
working-directory: ./terraform
- name: Terraform Plan
run: |
terraform plan \
-var="region=us-central1" \
-var="project_id=$GCP_PROJECT_ID" \
-var="container_image=us.gcr.io/$GCP_PROJECT_ID/nodeappimage:$IMAGE_TAG" \
-out=PLAN
working-directory: ./terraform
- name: Terraform Apply
run: terraform apply PLAN
working-directory: ./terraform