Chuyển tới nội dung

XÂY DỰNG HỆ THỐNG HYBRID CLOUD DR & GITOPS

Mục tiêu: Lab thực chiến: Kết hợp AWS EKS, K8s On-Premise và GitOps Flow

1. TỔNG QUAN DỰ ÁN

Bối cảnh: Công ty “Perfectkey” yêu cầu xây dựng một hệ thống triển khai ứng dụng (CI/CD) đảm bảo tính sẵn sàng cao (High Availability) và khả năng phục hồi sau thảm họa (Disaster Recovery).

Thách thức chính:

  • Hệ thống chính (Primary) chạy trên Cloud (AWS EKS) để phục vụ khách hàng toàn cầu với tốc độ cao.
  • Hệ thống dự phòng (DR Site) chạy tại văn phòng (On-Premise) để đề phòng trường hợp Cloud bị sập (Region Outage) hoặc đứt cáp quang biển.
  • Yêu cầu đặc biệt: Code nguồn phải được bảo mật nội bộ. Chỉ phiên bản Release mới được đẩy ra Public Cloud.

2. YÊU CẦU KỸ THUẬT (REQUIREMENTS)

Học viên cần hoàn thành các hạng mục sau:

A. Hạ tầng (Infrastructure Setup)

🏢 On-Premise (DR Site)

  • Dựng cụm K8s Local.
  • Dựng bộ công cụ Core: JenkinsGitLabHarbor.
  • Thiết lập Cloudflare Tunnel để:
    • Public DR App: dr.diendo.com.vn -> Trỏ về Nginx Ingress Local.
    • (Optional) Kết nối ArgoCD Cloud về GitLab Local.

☁️ Cloud (Primary Site)

  • Dựng AWS EKS (Production).
  • Tạo AWS ECR (Registry).
  • Tạo GitHub Repo (Public/Private) để chứa Config cho Production.

B. Quy trình CI/CD (Pipeline Workflow)

Sơ đồ luồng CI/CD từ Local lên Cloud

Viết Jenkinsfile thực hiện luồng công việc sau:

  1. Giai đoạn Phát triển (Local Phase):
    • Dev push code vào GitLab.
    • Jenkins build Docker Image -> Push vào Harbor.
    • Jenkins update manifest trên GitLab -> ArgoCD Local sync về K8s Local.
    • Mục tiêu: Dev và QC test nội bộ tốc độ cao.
  2. Giai đoạn Kiểm duyệt (Quality Gate):
    • Pipeline dừng lại, chờ QC bấm nút “Approve”.
  3. Giai đoạn Release (Promotion Phase):
    • Jenkins đẩy Image từ Local lên AWS ECR.
    • Jenkins đẩy Config (file values-prod.yaml) lên GitHub.
    • ArgoCD Cloud sync từ GitHub -> Deploy lên EKS.
    • ArgoCD Local sync từ GitLab -> Deploy lên K8s Local (Namespace prod-dr) để làm Backup nóng.

C. Kịch bản DR (Disaster Recovery Test)

  1. Truy cập Web chính (app.diendo.pro.vn) đang chạy trên AWS.
  2. Giả lập sự cố: Tắt cụm EKS (hoặc scale về 0).
  3. Failover: Vào Cloudflare DNS, chuyển traffic trỏ về Tunnel (DR Site).
  4. Kết quả: Web vẫn hoạt động bình thường (chạy từ máy Local).

3. Thực hiên

A. Cài đăt hạ tầng (Infrastruture)

On-Premise

  • Cài đặt cụm k8s (xem tại đây)
  • Cài đặt gitlab,npm.

Tạo proxy_network trên docker để kết nối các component của docker

docker network create -d macvlan --subnet=10.100.1.0/24 --ip-range=10.100.1.48/28 --gateway=10.100.1.8 -o macvlan_mode=bridge -o parent=ens160 proxy_network

file docker-compose.yaml

version: '3.8'

services:
  # --- SERVICE 1: TECHNITIUM DNS SERVER (IP: .50) ---
  dns-server:
    image: 'technitium/dns-server:latest'
    container_name: dns-server
    hostname: dns.diendo.pro.vn
    restart: always
    ports: ["5380:5380/tcp"] # Web UI
    volumes: ["./technitium-config:/etc/dns"]
    environment: ["TZ=Asia/Ho_Chi_Minh"]
    networks:
      proxy_network: { ipv4_address: 10.100.1.50 }

  # --- SERVICE 2: NGINX PROXY MANAGER (NPM) (IP: .55) ---
  nginx-proxy-manager:
    image: 'jc21/nginx-proxy-manager:latest'
    container_name: nginx-proxy-manager
    restart: always
    ports:
      - '80:80'         # HTTP
      - '81:81'         # Web UI
      - '443:443'       # HTTPS
    environment:
      DB_MYSQL_HOST: "10.100.1.54"
      DB_MYSQL_PORT: 3306
      DB_MYSQL_USER: "npm"
      DB_MYSQL_PASSWORD: "npm_password"
      DB_MYSQL_NAME: "npm_db_1"
    volumes:
      - ./nginx/data:/data
      - ./nginx/letsencrypt:/etc/letsencrypt
    networks:
      proxy_network:
        ipv4_address: 10.100.1.55

  npm-db-1:
    image: 'mariadb:10.5'
    container_name: npm-db-1
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: 'npm_rootpass'
      MYSQL_DATABASE: 'npm_db_1'
      MYSQL_USER: 'npm'
      MYSQL_PASSWORD: 'npm_password'
    volumes:
      - ./mysql:/var/lib/mysql
    networks:
      proxy_network:
        ipv4_address: 10.100.1.54

  rancher:
    image: rancher/rancher:v2.12.0
    container_name: rancher
    restart: always
    privileged: true
    environment:
      CATTLE_BOOTSTRAP_PASSWORD: "BeoBeo...@2025"
    volumes:
      - ./rancher/data:/var/lib/rancher
    networks:
      proxy_network:
        ipv4_address: 10.100.1.56
    ports:
      - "443:443"

  # --- SERVICE 3: GITLAB-EE (IP: .51) ---
  gitlab:
    image: 'gitlab/gitlab-ee:latest'
    container_name: gitlab
    restart: always
    hostname: 'gitlab.diendo.pro.vn'
    environment:
      GITLAB_OMNIBUS_CONFIG: |
        external_url 'http://gitlab.diendo.pro.vn' # Initial, overridden by gitlab.rb
        gitlab_rails['initial_root_password'] = 'BeoBeo...@2025'
    volumes:
      - './gitlab/config:/etc/gitlab' # Source of Truth for config
      - './gitlab/logs:/var/log/gitlab'
      - './gitlab/data:/var/opt/gitlab'
    shm_size: '256m'
    dns: ["10.100.1.50", "1.1.1.1"] # Use internal DNS
    ports: ['22:22', '5050:5050'] # SSH, Registry internal port
    networks:
      proxy_network: { ipv4_address: 10.100.1.51 }

  # --- SERVICE 4: GITLAB RUNNER (IP: .52) - Optional ---
  gitlab-runner:
    image: 'gitlab/gitlab-runner:latest'
    container_name: gitlab-runner
    privileged: true
    depends_on: [gitlab]
    restart: always
    volumes: ['./runner-config:/etc/gitlab-runner', '/var/run/docker.sock:/var/run/docker.sock'] # SECURITY RISK
    dns: ["10.100.1.50", "1.1.1.1"]
    networks:
      proxy_network: { ipv4_address: 10.100.1.52 }

  # --- SERVICE 5: JENKINS (IP: .53) - All-in-One ---
  jenkins:
    build: { context: ./jenkins } # Use custom Dockerfile
    # container_name removed to fix DNS webhook issue
    restart: unless-stopped
    privileged: true # Insecure: Needed for docker.sock
    user: root       # Insecure: Needed for docker.sock permissions
    volumes:
      - ./kube/.kube:/root/.kube        # Optional K8s config
      - ./kube/.minikube:/root/.minikube # Optional K8s config
      - ./jenkins/jenkins_home:/var/jenkins_home # Persist Jenkins data
      - '/var/run/docker.sock:/var/run/docker.sock' # Insecure: Access host Docker
    dns: ["10.100.1.50", "1.1.1.1"] # Use internal DNS
    networks:
      proxy_network: { ipv4_address: 10.100.1.53 }

# --- GLOBAL NETWORK CONFIGURATION ---
networks:
  proxy_network:
    external: true # Use the pre-created MACVLAN network

Chạy lệnh docker compose up -d

Cài đặt jenkins qua docker. File docker-compose.yaml

# Start from the official Jenkins LTS image using JDK 17
FROM jenkins/jenkins:lts-jdk17
# Switch to root user to install packages
USER root
# Install prerequisites and Docker GPG key
RUN apt-get update && apt-get install -y ca-certificates curl gnupg
RUN install -m 0755 -d /etc/apt/keyrings
RUN curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
RUN chmod a+r /etc/apt/keyrings/docker.asc
# Add Docker repository
RUN echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  tee /etc/apt/sources.list.d/docker.list > /dev/null
# Install Docker CLI
RUN apt-get update && apt-get install -y docker-ce-cli
RUN curl -fsSL "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" -o /usr/local/>
        && chmod +x /usr/local/bin/kubectl /usr/bin/docker || true \
        && apt-get clean && rm -rf /var/lib/apt/lists/*

ENV HELM_VERSION=v3.17.4
RUN curl -L "https://get.helm.sh/helm-${HELM_VERSION}-linux-amd64.tar.gz" | tar -xz \
    && mv linux-amd64/helm /usr/local/bin/helm \
    && chmod +x /usr/local/bin/helm \
    && helm plugin install https://github.com/chartmuseum/helm-push

# Switch back to jenkins user
USER jenkins

# Final verification check
RUN kubectl version --client && helm version

Chạy lệnh docker compose up -d

Đến đây phần hạ

💡 Gợi ý 1: Chiến lược “Dual Push” trong Jenkins

  • Vấn đề: Làm sao để đẩy image lên cả Harbor và ECR trong cùng 1 pipeline?
  • Hint:
    • Dùng docker tag để tạo 2 cái tên cho cùng 1 ID ảnh:- harbor.local/myapp:v1– 123456.dkr.ecr...amazonaws.com/myapp:v1
    • Dùng docker login lần lượt vào Harbor và ECR để push.
    • Mẹo: Có thể dùng công cụ skopeo để copy ảnh giữa các registry.

🔑 Gợi ý 2: Đồng bộ Git (GitLab -> GitHub)

  • Vấn đề: Làm sao Jenkins (Local) có quyền ghi vào GitHub (Cloud)?
  • Hint: Tạo Personal Access Token (PAT) trên GitHub -> Lưu vào Jenkins Credentials -> Dùng lệnh git push có kèm token.

🔄 Gợi ý 3: Cấu hình ArgoCD “Chia đôi”

  • ArgoCD Local: Trỏ source về GitLab.
  • ArgoCD Cloud: Trỏ source về GitHub.
  • Lý do: Để khi Local mất mạng, Cloud vẫn sống nhờ GitHub.
Liên hệ