Skip to content

Commit 4978ff2

Browse files
committed
feat(v3): K8S IaC + Terraform CI/CD 워크플로우 추가
- Terraform V3 K8S IaC 전체 코드 (6 모듈, 54 리소스) - k8s-networking, security-groups, iam, nat-instance, k8s-nodes, alb - Ansible 플레이북 (kubeadm 클러스터 부트스트랩) - GitHub Actions 워크플로우 (PR→plan 자동, apply 수동 트리거) - OIDC 기반 AWS 인증 (OIDC_terraform_v3 Role) - 설계 문서 업데이트 (NAT 단일 운영, ASG 래핑)
1 parent c6f886f commit 4978ff2

41 files changed

Lines changed: 1989 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
name: "Terraform V3 K8S"
2+
3+
on:
4+
pull_request:
5+
branches: [main]
6+
paths: ["IaC/3-v3/aws/**"]
7+
workflow_dispatch:
8+
inputs:
9+
action:
10+
description: "실행할 Terraform 작업"
11+
required: true
12+
type: choice
13+
options:
14+
- plan
15+
- apply
16+
17+
concurrency:
18+
group: terraform-v3-k8s-dev
19+
cancel-in-progress: false
20+
21+
permissions:
22+
id-token: write
23+
contents: read
24+
pull-requests: write
25+
26+
env:
27+
TF_VERSION: "1.14.6"
28+
WORKING_DIR: "IaC/3-v3/aws/environments/k8s-dev"
29+
AWS_REGION: "ap-northeast-2"
30+
31+
jobs:
32+
# ──────────────────────────────────────────────
33+
# Plan: PR 또는 수동 트리거
34+
# ──────────────────────────────────────────────
35+
plan:
36+
name: "Terraform Plan"
37+
runs-on: ubuntu-latest
38+
outputs:
39+
plan_exitcode: ${{ steps.plan.outputs.exitcode }}
40+
steps:
41+
- uses: actions/checkout@v4
42+
43+
- uses: aws-actions/configure-aws-credentials@v4
44+
with:
45+
role-to-assume: ${{ secrets.TF_AWS_ROLE_ARN }}
46+
aws-region: ${{ env.AWS_REGION }}
47+
48+
- uses: hashicorp/setup-terraform@v3
49+
with:
50+
terraform_version: ${{ env.TF_VERSION }}
51+
52+
- name: Terraform fmt
53+
id: fmt
54+
run: terraform fmt -check -recursive -diff
55+
working-directory: IaC/3-v3/aws
56+
continue-on-error: true
57+
58+
- name: Terraform Init
59+
id: init
60+
run: terraform init -backend-config=backend.hcl -backend-config="profile=" -input=false
61+
working-directory: ${{ env.WORKING_DIR }}
62+
63+
- name: Terraform Validate
64+
id: validate
65+
run: terraform validate -no-color
66+
working-directory: ${{ env.WORKING_DIR }}
67+
68+
- name: Terraform Plan
69+
id: plan
70+
run: |
71+
terraform plan \
72+
-var="vpc_id=${{ secrets.K8S_DEV_VPC_ID }}" \
73+
-var="ssl_certificate_arn=${{ secrets.K8S_DEV_SSL_CERT_ARN }}" \
74+
-input=false -no-color -detailed-exitcode -out=tfplan \
75+
2>&1 | tee plan_output.txt
76+
working-directory: ${{ env.WORKING_DIR }}
77+
continue-on-error: true
78+
79+
- name: Comment Plan on PR
80+
if: github.event_name == 'pull_request'
81+
uses: actions/github-script@v7
82+
with:
83+
script: |
84+
const fs = require('fs');
85+
const planPath = '${{ env.WORKING_DIR }}/plan_output.txt';
86+
let plan = fs.existsSync(planPath) ? fs.readFileSync(planPath, 'utf8') : 'Plan output not available';
87+
if (plan.length > 60000) plan = plan.substring(0, 60000) + '\n... (truncated)';
88+
89+
const body = `### Terraform Plan Result
90+
| Step | Status |
91+
|------|--------|
92+
| Format | ${'${{ steps.fmt.outcome }}' === 'success' ? '✅' : '❌'} |
93+
| Init | ${'${{ steps.init.outcome }}' === 'success' ? '✅' : '❌'} |
94+
| Validate | ${'${{ steps.validate.outcome }}' === 'success' ? '✅' : '❌'} |
95+
| Plan | ${'${{ steps.plan.outcome }}' === 'success' ? '✅ (no changes)' : '⚠️ (has changes)'} |
96+
97+
<details><summary>Plan Output</summary>
98+
99+
\`\`\`terraform
100+
${plan}
101+
\`\`\`
102+
103+
</details>
104+
105+
*Pushed by: @${{ github.actor }}*`;
106+
107+
const { data: comments } = await github.rest.issues.listComments({
108+
owner: context.repo.owner, repo: context.repo.repo,
109+
issue_number: context.issue.number
110+
});
111+
const bot = comments.find(c => c.user.type === 'Bot' && c.body.includes('Terraform Plan Result'));
112+
const params = { owner: context.repo.owner, repo: context.repo.repo, body };
113+
if (bot) { await github.rest.issues.updateComment({ ...params, comment_id: bot.id }); }
114+
else { await github.rest.issues.createComment({ ...params, issue_number: context.issue.number }); }
115+
116+
- name: Fail on Error
117+
if: steps.plan.outputs.exitcode == '1'
118+
run: exit 1
119+
120+
- name: Upload Plan
121+
if: steps.plan.outputs.exitcode == '2'
122+
uses: actions/upload-artifact@v4
123+
with:
124+
name: tfplan
125+
path: ${{ env.WORKING_DIR }}/tfplan
126+
retention-days: 5
127+
128+
# ──────────────────────────────────────────────
129+
# Apply: 수동 트리거만 (workflow_dispatch + action=apply)
130+
# ──────────────────────────────────────────────
131+
apply:
132+
name: "Terraform Apply"
133+
needs: plan
134+
if: github.event_name == 'workflow_dispatch' && github.event.inputs.action == 'apply' && needs.plan.outputs.plan_exitcode == '2'
135+
runs-on: ubuntu-latest
136+
steps:
137+
- uses: actions/checkout@v4
138+
139+
- uses: aws-actions/configure-aws-credentials@v4
140+
with:
141+
role-to-assume: ${{ secrets.TF_AWS_ROLE_ARN }}
142+
aws-region: ${{ env.AWS_REGION }}
143+
144+
- uses: hashicorp/setup-terraform@v3
145+
with:
146+
terraform_version: ${{ env.TF_VERSION }}
147+
terraform_wrapper: false
148+
149+
- name: Terraform Init
150+
run: terraform init -backend-config=backend.hcl -backend-config="profile=" -input=false
151+
working-directory: ${{ env.WORKING_DIR }}
152+
153+
- uses: actions/download-artifact@v4
154+
with:
155+
name: tfplan
156+
path: ${{ env.WORKING_DIR }}
157+
158+
- name: Terraform Apply
159+
run: terraform apply -input=false tfplan
160+
working-directory: ${{ env.WORKING_DIR }}

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,5 @@ service-account*.json
5959
*.tfplan
6060
*.plan
6161
*.tfplan.json
62+
tfplan
6263
wiki/design/step5/design-step5_old.md
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# ============================================================
2+
# V3 K8S Ansible — 전체 그룹 변수
3+
# Branch: feat/v3-k8s-iac
4+
# ============================================================
5+
6+
# K8S 버전
7+
k8s_version: "1.31"
8+
k8s_minor: "1.31.0"
9+
10+
# 네트워크
11+
pod_cidr: "192.168.0.0/16" # Calico default
12+
service_cidr: "10.96.0.0/12" # kubeadm default
13+
calico_vxlan_mtu: 8951 # AWS Jumbo Frame(9001) - VXLAN overhead(50)
14+
15+
# Gateway Fabric
16+
gateway_nodeport_http: 30080
17+
gateway_nodeport_https: 30443
18+
19+
# containerd
20+
containerd_version: "1.7"
21+
22+
# 클러스터
23+
cluster_name: "dojangkok-v3"
24+
25+
# SSM을 통한 접근 (SSH 대신)
26+
ansible_connection: aws_ssm
27+
ansible_aws_ssm_region: ap-northeast-2
28+
ansible_aws_ssm_profile: tf
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# ============================================================
2+
# V3 K8S Ansible — Dynamic Inventory (AWS EC2 plugin)
3+
# k8s:cluster-name 태그로 클러스터 노드 자동 인식
4+
# Branch: feat/v3-k8s-iac
5+
# ============================================================
6+
plugin: amazon.aws.aws_ec2
7+
8+
regions:
9+
- ap-northeast-2
10+
11+
filters:
12+
"tag:k8s:cluster-name": "dojangkok-v3"
13+
instance-state-name: running
14+
15+
keyed_groups:
16+
# k8s:role 태그 → role_control_plane / role_worker 그룹
17+
- key: tags['k8s:role'] | default('unknown')
18+
prefix: role
19+
separator: "_"
20+
# k8s:nodepool 태그 → nodepool_default / nodepool_system 그룹
21+
- key: tags['k8s:nodepool'] | default('default')
22+
prefix: nodepool
23+
separator: "_"
24+
25+
hostnames:
26+
- private-ip-address
27+
28+
compose:
29+
ansible_host: private_ip_address
30+
ansible_user: ubuntu
31+
k8s_role: tags['k8s:role']
32+
k8s_nodepool: tags['k8s:nodepool']
33+
node_az: placement.availability_zone
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# ============================================================
2+
# V3 K8S Ansible — 전체 클러스터 부트스트랩 플레이북
3+
# Branch: feat/v3-k8s-iac
4+
# ============================================================
5+
6+
# Phase 1: 전체 노드 공통 설정
7+
- name: Common setup (all nodes)
8+
hosts: all
9+
become: true
10+
roles:
11+
- common
12+
- containerd
13+
- kubeadm-prereqs
14+
15+
# Phase 2: Control Plane 초기화
16+
- name: Initialize Control Plane
17+
hosts: role_control_plane
18+
become: true
19+
roles:
20+
- kubeadm-init
21+
22+
# Phase 3: Worker 노드 Join
23+
- name: Join Worker nodes
24+
hosts: role_worker
25+
become: true
26+
roles:
27+
- kubeadm-join
28+
29+
# Phase 4: CNI + 클러스터 컴포넌트 (CP에서 실행)
30+
- name: Install cluster components
31+
hosts: role_control_plane
32+
become: true
33+
roles:
34+
- calico
35+
- ebs-csi
36+
- gateway-fabric
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# ============================================================
2+
# V3 K8S Ansible — Calico CNI (Tigera Operator, VXLAN)
3+
# Branch: feat/v3-k8s-iac
4+
# ============================================================
5+
6+
- name: Install Tigera Operator
7+
become_user: ubuntu
8+
command: >
9+
kubectl create -f
10+
https://raw.githubusercontent.com/projectcalico/calico/v3.28.0/manifests/tigera-operator.yaml
11+
register: tigera_result
12+
failed_when: tigera_result.rc != 0 and 'already exists' not in tigera_result.stderr
13+
changed_when: "'created' in tigera_result.stdout"
14+
15+
- name: Apply Calico custom resource
16+
become_user: ubuntu
17+
copy:
18+
content: |
19+
apiVersion: operator.tigera.io/v1
20+
kind: Installation
21+
metadata:
22+
name: default
23+
spec:
24+
calicoNetwork:
25+
ipPools:
26+
- cidr: {{ pod_cidr }}
27+
encapsulation: VXLAN
28+
natOutgoing: Enabled
29+
mtu: {{ calico_vxlan_mtu }}
30+
dest: /tmp/calico-installation.yaml
31+
32+
- name: Apply Calico installation
33+
become_user: ubuntu
34+
command: kubectl apply -f /tmp/calico-installation.yaml
35+
36+
- name: Wait for Calico pods to be ready
37+
become_user: ubuntu
38+
command: >
39+
kubectl wait --for=condition=Ready pods
40+
--all -n calico-system
41+
--timeout=300s
42+
retries: 3
43+
delay: 10
44+
register: calico_ready
45+
until: calico_ready.rc == 0
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# ============================================================
2+
# V3 K8S Ansible — Common (전체 노드 공통 설정)
3+
# Branch: feat/v3-k8s-iac
4+
# ============================================================
5+
6+
- name: Update apt cache
7+
apt:
8+
update_cache: true
9+
cache_valid_time: 3600
10+
11+
- name: Install required packages
12+
apt:
13+
name:
14+
- apt-transport-https
15+
- ca-certificates
16+
- curl
17+
- gnupg
18+
- socat
19+
- conntrack
20+
- ipset
21+
state: present
22+
23+
- name: Disable swap
24+
command: swapoff -a
25+
changed_when: false
26+
27+
- name: Remove swap from fstab
28+
lineinfile:
29+
path: /etc/fstab
30+
regexp: '\sswap\s'
31+
state: absent
32+
33+
- name: Load required kernel modules
34+
modprobe:
35+
name: "{{ item }}"
36+
loop:
37+
- overlay
38+
- br_netfilter
39+
40+
- name: Persist kernel modules
41+
copy:
42+
content: |
43+
overlay
44+
br_netfilter
45+
dest: /etc/modules-load.d/k8s.conf
46+
47+
- name: Set sysctl params for K8S
48+
sysctl:
49+
name: "{{ item.key }}"
50+
value: "{{ item.value }}"
51+
sysctl_file: /etc/sysctl.d/k8s.conf
52+
reload: true
53+
loop:
54+
- { key: "net.bridge.bridge-nf-call-iptables", value: "1" }
55+
- { key: "net.bridge.bridge-nf-call-ip6tables", value: "1" }
56+
- { key: "net.ipv4.ip_forward", value: "1" }
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
- name: restart containerd
2+
systemd:
3+
name: containerd
4+
state: restarted
5+
daemon_reload: true

0 commit comments

Comments
 (0)