One Logical Infrastructure, Many Physical Realities: A YANG-Driven Hybrid Cloud with K3s
Model is the Control Plane, not the Reality Plane.
본 프로젝트는 이 한 문장을 증명하기 위한 실험실입니다. 지리적으로 떨어진 AWS (ARM/x86)와 온프레미스 자원이라는 현실 (RAW)을, YANG 모델이라는 정교한 질서 (PURE) 아래 하나의 클러스터로 수렴시키는 과정을 다룹니다.
YANG 모델링을 통한 추상화부터 데이터 검증, 스토리지 통합, 그리고 Terraform/Ansible을 활용한 자동화까지 하이브리드 클라우드 구축의 전 과정을 다룹니다. 파편화된 물리적 자원들을 단일 진실 원천 (SSoT)인 모델을 중심으로 결합하여, 설계가 곧 실제 인프라가 되는 모델-드리븐 인프라 (MDI)를 구현합니다.
-
Unified SSoT: YANG 모델링을 통해 이기종 플랫폼 (AWS/On-Premise)의 리소스 명세를 통합하고 플랫폼 독립적인 설계도를 완성했습니다.
-
Mesh Networking Backbone: Tailscale을 활용해 모든 노드를 하나의 오버레이 네트워크로 연결하여 지리적 제약이 없는 일관된 통신 환경을 구축했습니다.
-
Storage Abstraction: JuiceFS를 통해 하이브리드 환경의 스토리지를 논리적으로 통합하여, 모든 노드에서 즉시 접근 가능한 공유 저장소를 확보했습니다.
-
End-to-End MDI Pipeline: YANG 설계도가 Terraform, Shell Script, Ansible을 거쳐 실제 인프라로 자동 전환되는 완전한 인프라 파이프라인을 시연했습니다.
Usage: make [target]
Targets:
help - 이 도움말 메시지를 출력합니다.
all - 모든 과정을 실행합니다.
keygen - [Phase 0] SSH 키 페어를 생성합니다.
lint - [Phase 2] YANG 모델 검사 및 JSON 검증을 실행합니다.
lint-test - [Phase 2] 에러가 있는 JSON 파일로 YANG 모델 검사를 테스트합니다.
provision - [Phase 4] AWS/On-premises 인프라 프로비저닝을 실행합니다.
bootstrap - [Phase 5] Ansible playbook을 실행하여 bootstrap을 수행합니다.
aws-purge - [Phase 4] AWS infrastructure를 제거합니다.
Phase 0. Physical Inventory & Resource Specification
Phase 1. Logical Abstraction via YANG Modeling
Phase 2. Data Integrity & Schema Validation
Phase 3. Storage Abstraction: JuiceFS Infrastructure Setup
Phase 4. Automating Node Provisioning with Terraform & Shell Scripting
Phase 5. Ansible-Driven Bootstrapping & Configuration Management
모든 노드 (AWS & On-Premise)의 통합 관리를 위해 공통 SSH Key Pair를 생성합니다.
# 로컬에서 SSH Key Pair 생성
mkdir -p ./00-key
ssh-keygen -t ed25519 -f ./00-key/hybrid-cloud -N ""
# 생성된 공개 키 확인
cat ./00-key/hybrid-cloud.pub| Location | IP | Port | Compute | Arch | Burstable | Cache Quota |
|---|---|---|---|---|---|---|
| Site A | 192.168.1.202 | 22 | 4 vCPU / 8GB RAM | x86_64 | No | 30GB NVME |
| Site B | 192.168.1.203 | 22 | 4 vCPU / 8GB RAM | x86_64 | No | 20GB SSD |
| AWS | Dynamic | 22 | t4g.medium | arm64 | Yes | 5GB EBS |
| AWS | Dynamic | 22 | t3.medium | x86_64 | Yes | 5GB EBS |
AWS의 IP 주소는 Terraform 프로비저닝 후 동적으로 할당됩니다.
| Location | Node Name | Node Role | Bootstrap Role | Strategy |
|---|---|---|---|---|
| Site A | site-a-node | control-plane | join | HA을 위한 보조 제어 노드 |
| Site B | site-b-node | worker | join | 확장된 워커 노드 |
| AWS | t4g-seed | control-plane | init | 클러스터 시드 노드 |
| AWS | t4g-a | worker | join | 확장된 워커 노드 |
| AWS | t4g-b | worker | join | 확장된 워커 노드 |
| AWS | t3-a | control-plane | join | 확장된 보조 제어 노드 |
| AWS | t3-b | worker | join | 확장된 워커 노드 |
| AWS | t3-c | worker | join | 확장된 워커 노드 |
Step 1. Base Type Definitions: nodes/common-types.yang
-
Platform (AWS/On-Premise), Arch, K3s Role 등에 대한 표준 데이터 타입을 정의합니다.
-
AWS 인스턴스 타입은 정규표현식을 통해 medium 이하 규격으로 엄격히 제한됩니다.
Step 2. Compute Resource Abstraction: nodes/res-compute.yang
-
플랫폼별 연산 자원 명세를 담당하며, Platform 값에 따라 입력 항목을 동적으로 강제합니다.
-
AWS: instance-type 기반의 규격화된 자원 할당.
-
On-Premise: vCPU 및 Memory 범위를 직접 지정하여 하드웨어 제약 내에서 자원을 선언합니다.
Step 3. Network Perimeter & Policy Modeling: nodes/res-network.yang
-
초기 프로비저닝 (Underlay)을 위한 접속 계정과 포트 정보를 정의합니다.
-
AWS의 Late Binding 전략과 On-Premise의 Early Binding 전략을 구분하여 모델링합니다.
Step 4. Distributed Storage Logic Modeling: nodes/res-storage.yang
-
분산 스토리지 (JuiceFS) 환경 구성을 위한 엔드포인트와 캐시 정책을 정의합니다.
-
자격 증명과 같은 민감 데이터를 YANG 명세에서 배제하고 앤서블의 동적 주입 방식으로 전환하여, 설계도의 범용성을 높이고 보안 사고를 원천 차단합니다.
Step 5. Holistic Cluster Integration: nodes/hybrid-cloud.yang
-
개별 리소스 모델을 통합하여 하이브리드 클러스터의 단일 진실 원천 (SSoT)을 구축합니다.
-
각 노드의 역할 할당 (control-plane, worker)과 부트스트랩 전략 (init, join)을 명세하여, 클러스터 부트스트래핑과 확장 시나리오를 지원합니다.
Step 6. Provider-Specific Extensions: providers/aws-provider.yang
-
AWS 프로비저닝에 특화된 YANG 모델로, AWS 고유의 자원 명세와 제약 조건을 포함합니다.
-
Region, VPC, Subnet, Security Group 등 AWS 인프라 구성 요소에 대한 세부적인 모델링을 통해, Terraform 코드와의 완벽한 매핑을 지원합니다.
# 레포지토리 클론
git clone https://github.com/CESNET/libyang.git
# 필요 패키지 설치
brew install cmake pcre2 pkg-config
# 빌드 및 설치
mkdir build && cd build
cmake -DCMAKE_INSTALL_PREFIX=/usr/local \
-DCMAKE_INSTALL_RPATH="/usr/local/lib" \
-DCMAKE_INSTALL_RPATH_USE_LINK_PATH=TRUE ..
make -j$(sysctl -n hw.ncpu)
sudo make install# 설치 확인
yanglint -version
# yanglint 4.2.2-
YANG 모델의 계층적 구조와 논리적 일관성을 검증하기 위해
yanglint의 트리 출력 기능을 활용합니다.yanglint -f tree ./01-schema/nodes/hybrid-cloud.yang yanglint -f tree ./01-schema/providers/aws-provider.yang
-
YANG 모델에 에러가 있는 경우,
yanglint가 상세한 오류 메시지를 제공하여 문제를 쉽게 파악할 수 있습니다.libyang err : Invalid keyword ";" as a child of "leaf". (Line number 37.) libyang err : Importing "res-compute" module failed. YANGLINT[E]: Processing schema module from ./01-schema/nodes/hybrid-cloud.yang failed. -
YANG 모델의 계층적 구조는 다음과 같이 시각적으로 표현됩니다.
module: hybrid-cloud +--rw cluster +--rw node* [name] +--rw name string +--rw k8s-role-assignment | +--rw node-role? ct:k8s-node-role | +--rw bootstrap-role? ct:k8s-bootstrap-role +--rw compute | +--rw platform ct:node-platform | +--rw arch ct:cpu-arch | +--rw (platform-spec) | +--:(aws) | | +--rw instance-type ct:aws-instance-type | | +--rw ebs-size uint16 | +--:(on-premise) | +--rw vcpu uint8 | +--rw memory uint8 +--rw network | +--rw ssh-user string | +--rw ssh-port inet:port-number | +--rw (bootstrap-strategy) | +--:(aws-strategy) | | +--rw public-ip-required? boolean | | +--rw use-eip? boolean | +--:(on-premise-strategy) | +--rw bootstrap-ip inet:ip-address +--rw storage +--rw s3-endpoint? string +--rw redis-endpoint? string +--rw cache-size? uint32module: aws-provider +--rw aws-config +--rw region? string +--rw vpc-cidr? inet:ipv4-prefix +--rw public-subnet-cidr? inet:ipv4-prefix +--rw private-subnet-cidr? inet:ipv4-prefix +--rw public-key-dir? string
for f in 02-inventory/nodes/*.json; do \
yanglint -p 01-schema/nodes 01-schema/nodes/hybrid-cloud.yang "$f" && \
echo "✅ YANG Lint Pass: $f" || { echo "❌ YANG Lint Fail: $f" && break; }; \
done✅ YANG Lint Pass: 02-inventory/nodes/site-a-node.json
✅ YANG Lint Pass: 02-inventory/nodes/site-b-node.json
✅ YANG Lint Pass: 02-inventory/nodes/t3-a.json
✅ YANG Lint Pass: 02-inventory/nodes/t3-b.json
✅ YANG Lint Pass: 02-inventory/nodes/t3-c.json
✅ YANG Lint Pass: 02-inventory/nodes/t4g-a.json
✅ YANG Lint Pass: 02-inventory/nodes/t4g-b.json
✅ YANG Lint Pass: 02-inventory/nodes/t4g-seed.json
for f in 02-inventory/providers/*.json; do \
yanglint -p 01-schema/providers 01-schema/providers/aws-provider.yang "$f" && \
echo "✅ YANG Lint Pass: $f" || { echo "❌ YANG Lint Fail: $f" && break; }; \
done✅ YANG Lint Pass: 02-inventory/providers/aws.jsonJSON 데이터에 에러가 있는 경우, yanglint가 상세한 오류 메시지를 제공하여 문제를 쉽게 파악할 수 있습니다.
-
예시:
instance-type이t4g.medium을 초과하는 경우libyang err : Unsatisfied pattern - "t4g.large" does not conform to "[tcrm][1-8][a-z]*\.(nano|micro|small|medium)". (Schema location /hybrid-cloud:cluster/node/compute/platform-spec/aws/instance-type, data location /hybrid-cloud:compute, line number 19.) YANGLINT[E]: Failed to parse input data file "02-inventory/t4g-seed.json". ❌ YANG Lint Fail: 02-inventory/t4g-seed.json -
예시:
platform과instance-type간의 불일치libyang err : Architecture mismatch detected: 'arm64' platform requires 'g' instance types, while 'x86_64' platform cannot use 'g' instance types. (/hybrid-cloud:cluster/node[name='t3-a']/compute/instance-type) YANGLINT[E]: Failed to parse input data file "02-inventory/t3-a.json". ❌ YANG Lint Fail: 02-inventory/t3-a.jsonlibyang err : Architecture mismatch detected: 'arm64' platform requires 'g' instance types, while 'x86_64' platform cannot use 'g' instance types. (/hybrid-cloud:cluster/node[name='t4g-a']/compute/instance-type) YANGLINT[E]: Failed to parse input data file "02-inventory/t4g-a.json". ❌ YANG Lint Fail: 02-inventory/t4g-a.json -
예시: On-Premise 노드에
public-ip-required가true로 설정된 경우libyang err : Data for both cases "aws-strategy" and "on-premise-strategy" exist. (Schema location /hybrid-cloud:cluster/node/network/bootstrap-strategy, data location /hybrid-cloud:network, line number 27.) YANGLINT[E]: Failed to parse input data file "02-inventory/site-a-node.json". ❌ YANG Lint Fail: 02-inventory/site-a-node.json
make lint-test 명령을 통해 의도적으로 오류가 있는 JSON 파일을 검사하여, YANG 모델이 예상대로 제약 조건을 강제하는지 검증할 수 있습니다.
-
error/01-arm-x86-instance.json:
arm64아키텍처 노드에g가 없는x86_64전용 인스턴스 타입 (t3.medium)이 지정되어must구문을 위반한 경우 -
error/02-x86-arm-instance.json:
x86_64아키텍처 노드에 Graviton (arm64) 전용 인스턴스 타입 (t4g.small)이 지정되어 must 구문을 위반한 경우 -
error/03-invalid-platform.json:
common-types에 정의되지 않은 플랫폼 값 (azure)을 입력하여enumeration타입 제약을 위반한 경우 -
error/04-ebs-type-mismatch.json:
uint16타입인ebs-size필드에 숫자가 아닌 문자열 값 ("100GB")을 입력하여 데이터 타입 검증에 실패한 경우 -
error/05-wrong-case-data.json: 플랫폼이
aws임에도 불구하고on-premise케이스 전용 필드 (vcpu,memory)를 포함하여when조건부 로직을 위반한 경우 -
error/06-missing-mandatory-choice.json:
mandatory: true로 설정된platform-spec초이스 내의 필수 리프 (instance-type)를 누락한 경우 -
error/07-vcpu-out-of-range.json:
on-premise설정에서vcpu값을 허용 범위 (1..8)를 초과하는 값 (64)으로 설정하여range제약을 위반한 경우 -
error/08-instance-regex-mismatch.json: AWS 인스턴스 명명 규칙 패턴 (
[tcrm][1-8]...)에 맞지 않는 인스턴스 타입 (p3.2xlarge)을 입력하여re-match검증에 실패한 경우 -
error/09-missing-arch.json:
compute컨테이너 내에서 반드시 존재해야 하는arch리프를 누락하여mandatory제약을 위반한 경우 -
error/10-empty-node.json: 노드 정의 내에 필수 컨테이너인
compute와network섹션을 모두 누락하여 모델의 최소 구조 요건을 충족하지 못한 경우
컴퓨트 노드와 완전히 격리된 독립형 스토리지 엔진을 구축합니다. S3 호환 API (MinIO)와 고성능 메타데이터 엔진 (Redis)을 추상화된 자원으로 제공하여 하이브리드 클러스터의 데이터 일관성을 보장합니다.
-
docker-compose.yml을 활용하여 스토리지 백엔드 구성을 코드화합니다. -
Host OS 환경에 의존하지 않고, 컨테이너 기술을 통해 엔진의 배포와 버전 관리를 단순화합니다.
| Component | Service | Port | Backend Storage |
|---|---|---|---|
| Metadata Engine | Redis | 4279 | Docker Volume (on NVME) |
| Object Storage | MinIO | 4200 (S3 API) / 4201 (Dashboard) | ZFS Dataset |
-
MinIO의 데이터 영속성을 위해 ZFS Storage Pool을 직접 매핑하여 데이터 안정성과 성능을 극대화했습니다.
-
기존 서비스 및 시스템 서비스와의 포트 간섭을 원천 차단하기 위해 전용 포트를 할당했습니다.
AWS 자원 생성에만 집중하며,
user_data스크립트를 완전히 배제하여 인프라 프로비저닝과 노드 부트스트래핑의 경계를 명확히 분리합니다. 이는 가장 순수한 형태의 IaC로서, 플랫폼에 종속되지 않는 하이브리드 클라우드 구축의 기반이 됩니다.
-
JSON 인벤토리를
jsondecode로 해석하여 YANG 모델이 정의한 명세를 동적으로 추출합니다. -
설계도의 변경 사항이 인프라 설정에 즉시 반영되는 유연한 데이터 파이프라인을 구축합니다.
local.region을 통해 실행 리전을 동적으로 선언하고pathexpand로 SSH 키 경로의 호환성을 확보합니다.
-
VPC와 퍼블릭 서브넷을 구축하여 외부 통신 기반을 마련하고 인터넷 게이트웨이를 통해 트래픽 흐름을 완성합니다.
-
관리용 SSH와 하이브리드 메시 네트워크를 위한 Tailscale 포트만을 선별적으로 개방하여 보안을 극대화합니다.
-
생성된 VPC, 보안 그룹, 키 페어 등의 핵심 식별자를 출력하여 후속 단계의 동적 참조를 지원합니다.
-
aws-node프로비저닝과 Ansible 인벤토리 구성 시 실시간 인프라 정보를 제공하는 핵심 지점이 됩니다.
-
terraform_remote_state를 활용하여aws-base에서 생성된 VPC, 서브넷, 보안 그룹 정보를 실시간으로 참조합니다. -
주입된 JSON 매니페스트를 분석하여 아키텍처, 인스턴스 유형, EBS 크기 등 개별 노드의 물리적 사양을 결정합니다.
-
노드 명세의 아키텍처 정보를 필터로 사용하여 해당 규격에 최적화된 최신 Amazon Linux 2023 AMI를 동적으로 선택합니다.
-
베이스 레이어와 동일한 리전에 프로바이더를 배치하고, 생성된 노드의 퍼블릭 IP를 출력하여 후속 자동화 단계의 엔드포인트를 제공합니다.
-
user_data를 완전히 배제하여 인프라 프로비저닝과 소프트웨어 부트스트래핑의 관리 영역을 엄격하게 격리합니다. -
source_dest_check를 비활성화하여 인스턴스가 하이브리드 메시 네트워크 상의 패킷 라우팅 중계지 역할을 수행할 수 있도록 설정합니다.
Step 2. Unified Orchestration with Shell Script: dispatcher.sh
설계도 (JSON)가 인프라 실재로 이어지는 경로를 최소화한 경량 디스패처입니다. 모델 중심의 데이터를 기반으로 Terraform과 Shell Bridge를 동적으로 실행하여 하이브리드 노드 구축의 투명성과 실행 속도를 극대화했습니다.
-
기존 Python 기반의 복잡한 객체 생성 과정을 제거하고, Shell과 jq만으로 구성된 Direct-Path 구조를 채택했습니다.
-
중간 추상화 레이어를 최소화하여, YANG 모델 (JSON)에서 실제 인프라 명령 (Terraform/SSH)으로 이어지는 데이터 흐름을 투명하고 직관적으로 개선했습니다.
-
set -e와 실시간 에러 핸들링을 통해 프로비저닝 과정 중 발생하는 사소한 결함도 즉시 감지하여 인프라 오염을 원천 차단합니다.
-
jq를 활용해 YANG 모델의 계층 구조를 직접 탐색하고, 노드별 플랫폼 (aws,on-premise) 및 네트워크 속성을 실시간으로 추출합니다. -
추출된 메타데이터를 기반으로 Terraform의
-chdir경로와-var주입 값을 동적으로 결정하여, 하나의 스크립트로 이기종 환경을 통합 관리합니다. -
모델에서 정의된 기본값을 jq의 Fallback 연산자 (
//)로 구현하여 데이터 누락 시에도 안정적인 실행을 보장합니다.
-
모든 AWS 노드에 대해 고유한
.tfstate파일을 생성함으로써, 특정 노드의 변경이나 삭제가 전체 클러스터 상태에 영향을 주지 않는 수평적 확장성을 확보했습니다. -
realpath를 통한 절대 경로 추적 방식을 도입하여, 프로젝트 내 어느 디렉토리에서 실행하더라도 인벤토리와 프로비저닝 코드를 정확하게 결합합니다. -
Cloud (Terraform)와 On-Premise (SSH Bridge) 간의 실행 분기를 단일
case문으로 통합하여, 향후 신규 플랫폼 추가 시에도 코드 수정 범위를 최소화했습니다.
Step 3. Automated Infrastructure De-provisioner: purge_march.sh
YANG 설계도와 Terraform의 상태 파일 (
.tfstate)을 동적으로 결합하여, 모델이 정의한 인프라의 시작과 끝을 일관성 있게 관리합니다.
-
사용하지 않는 AWS 인프라를 즉시 회수하여 불필요한 과금을 방지하고 클라우드 자원을 효율적으로 관리합니다.
-
terraform destroy명령을 자동화하여 수동 작업 시 발생할 수 있는 자원 누락 문제를 원천 차단합니다.
-
각 노드별로 분리된
.tfstate파일을 기반으로 동작하므로, 전체 클러스터에 영향을 주지 않고 특정 노드의 자원만 안전하게 제거할 수 있습니다. -
실제
terraform destroy명령을 내리기 전 해당 노드의 상태 파일 존재 여부를 먼저 검사하여, 이미 제거된 자원에 대한 중복 실행 에러를 방지하는 방어적 로직을 갖췄습니다.
-
프로비저닝 단계와 동일한 매니페스트 경로 및 SSH 키 경로 변수를 Terraform에 주입합니다.
-
이를 통해 Terraform이 파괴 시점에도 동적 자원 이름을 정확히 계산하여, 이름 충돌 없이 대상 자원을 식별할 수 있도록 보장합니다.
Step 1. Dynamic Inventory Generation: inventory/resolver.py
수동으로 hosts 파일을 수정하는 전통적인 방식에서 벗어나, 코드가 인프라의 변화를 스스로 감지하고 환경 설정을 업데이트하는 Self-Healing Inventory를 구현했습니다.
-
.tfstate파일을 탐색하여 AWS에서 프로비저닝된 노드들의 공인 IP 주소를 하나의 Map으로 집계합니다. -
노드가 추가되거나 제거되더라도, 실행 시점의 Terraform 상태를 반영하므로 항상 최신의 인벤토리를 유지할 수 있습니다.
-
YANG 매니페스트와 Terraform의 상태 파일을 동시에 참조하여 Ansible이 이해할 수 있는
HostVars구조로 변환합니다. -
설계도에 정의된
role-assignment,storage,compute등의 메타데이터를 개별 노드의 변수로 완벽하게 이식합니다.
-
Ansible의 Dynamic Inventory 규격에 맞춘 JSON 출력을 생성하여,
_meta정보를 포함한 데이터를 생성합니다. -
aws,on_premise,server,agent등의 논리적 그룹핑을 자동으로 생성하여, 플레이북에서 유연하게 타겟팅할 수 있도록 지원합니다.
-
처리 과정과 디버깅 로그는
sys.stderr로 출력하여, Ansible이 JSON 데이터를 표준 출력 (sys.stdout)으로만 인식하도록 설계되었습니다. -
이를 통해 Ansible 파싱 에러를 방지하면서, 터미널로 실시간 수집 현황을 모니터링할 수 있도록 했습니다.
Ansible이 하이브리드 클라우드의 다양한 환경에서 일관된 방식으로 작동할 수 있도록, 핵심 설정과 공통 작업을 정의하는 단계입니다. 이는 이후의 플레이북들이 안정적으로 실행될 수 있는 기반을 마련합니다.
1. Ansible Core Configuration: ansible.cfg
-
동적 리졸버와의 연동, 보안 인증, 그리고 하이브리드 환경의 지연 시간을 극복하기 위한 성능 최적화 설정을 정의합니다.
[defaults] # 인벤토리 스크립트 경로 설정 inventory = ./inventory/resolver.py # SSH 개인 키 파일 경로 설정 private_key_file = ../00-key/hybrid-cloud # Vault 파일 경로 설정 vault_password_file = ./.vault_pass # SSH 호스트 키 검증 비활성화 host_key_checking = False # Python 인터프리터 자동 감지 설정 (불필요한 경고 메시지 제거) interpreter_python = auto_silent # 출력 형식 설정 (YAML) stdout_callback = ansible.builtin.default result_format = yaml # 콜백 플러그인 설정 (YAML) bin_ansible_callbacks = True # Ansible이 수집한 사실을 변수로 주입하지 않도록 설정 inject_facts_as_vars = False [ssh_connection] # SSH 연결 최적화 설정 pipelining = True # SSH 연결 재사용 설정 ssh_args = -o ControlMaster=auto -o ControlPersist=60s
2. Common Infrastructure Baseline: roles/common/tasks/main.yml
-
모든 노드가 갖춰야 할 최소 요건을 구성합니다.
-
node_spec기반의 호스트네임 설정 및/etc/hosts정비를 통해sudo지연과 이름 해석 에러를 원천 차단합니다. -
외부 저장소 및 API 통신을 위해 신뢰할 수 있는 DNS 설정을 강제 주입하여
apt/dnf업데이트의 안정성을 확보합니다. -
os_family및distribution팩트를 활용하여 OS별로roles/common/vars/main.yml에 정의된 패키지를 설치합니다. -
Amazon Linux 대응
-
Amazon Linux 2023의 기본 패키지인
curl-minimal은 일반curl설치 시 의존성 충돌을 일으킵니다. -
Amazon Linux 환경을 별도로 감지하여,
allowerasing: yes옵션을 통해 기존 패키지를 안전하게 교체하고 표준 도구 세트를 완성합니다.
-
Step 3. Ansible Playbook for Tailscale Mesh Network Setup: roles/tailscale/tasks/main.yml
모든 노드가 하나의 Overlay 네트워크로 연결하기 위한 Tailscale 설치 및 초기 설정을 담당합니다.
-
Tailscale 공식 설치 스크립트를 활용하여, Amazon Linux (ARM64)와 Debian (x86_64) 등 서로 다른 플랫폼과 아키텍처에 상관없이 일관된 설치 과정을 제공합니다.
-
creates: /usr/sbin/tailscale옵션을 통해 이미 설치된 노드에서의 중복 실행을 방지하여 멱등성을 확보합니다. -
tailscale status --json명령을 통해 현재 노드의 연결 상태를 JSON 형식으로 파악합니다. -
응답 JSON의
BackendState필드가"Running"이 아닌 경우에만tailscale up을 실행하도록 조건화하여, 이미 네트워크에 가입된 노드에 불필요한 가입 요청을 방지합니다.
-
ansible-vault로 암호화된 인증 키를 활용하며,no_log: true설정을 통해 민감한 키 정보가 앤서블 실행 로그에 남지 않도록 보안을 강화했습니다. -
BackendState != "Running"조건에 따라 선택적으로tailscale up명령을 실행하여, 노드를 Tailscale 네트워크에 연결합니다. -
--auth-key {{ tailscale_auth_key }}옵션을 주입하여 수동 인증 과정 없는 완전 자동화된 네트워크 가입을 실현합니다. -
리졸버에서 추출한
{{ node_spec.name }}을 호스트네임으로 지정하여, Tailscale 대시보드 내에서 노드 식별을 명확히 합니다. -
네트워크 가입 후 할당된
100.64.0.0/10대역의 사설 IP 주소를tailscale ip -4명령으로 실시간 확인합니다. -
연결이 완료될 때까지 최대 5회 재시도를 수행하여, 네트워크 인터페이스가 활성화되는 데 필요한 물리적 시간을 충분히 확보합니다.
-
ansible.builtin.set_fact모듈을 활용하여, 노드의 Tailscale IP 주소를ts_ip변수로 저장한 후 이후 플레이북에서 참조할 수 있게 합니다.
-
네트워크 가속 및 하드닝: 하이브리드 클라우드의 대역폭 성능을 극대화하기 위해 커널 수준에서 BBR 혼잡 제어 및 UDP/NIC Offload 튜닝을 자동 적용합니다.
-
프로세스 안정성:
OOMScoreAdjust및Nice값 조정을 통해 시스템 리소스 부족 시에도 네트워크 연결을 최우선으로 보호합니다.
Step 4. Ansible Playbook for JuiceFS Setup: roles/juicefs/tasks/main.yml
JuiceFS 클라이언트 설치 및 S3/Redis 엔드포인트 연결을 담당합니다.
-
JuiceFS 공식 설치 스크립트를 활용하여, Amazon Linux (ARM64)와 Debian (x86_64) 등 서로 다른 플랫폼과 아키텍처에 상관없이 일관된 설치 과정을 제공합니다.
-
juicefs_bin_path변수와 Ansible의creates옵션을 결합하여, 이미 바이너리가 존재하는 노드에서의 불필요한 재설치를 방지합니다. -
설치 완료 후
{{ juicefs_bin_path }} --version명령을 실행하여 JuiceFS 바이너리가 정상적으로 설치되었는지 검증합니다. -
ansible_user를 기준으로 마운트 디렉토리를 생성하고, 소유권을 사전에 설정하여 이후 프로세스에서 발생할 수 있는 권한 충돌을 예방합니다.
-
JSON 인벤토리의 S3/Redis 정보를 기반으로
juicefs format명령을 실행합니다:{{ juicefs_bin_path }} format \ --storage s3 \ --bucket "{{ storage_info.s3_url }}/jfs" \ --access-key "{{ minio_root_user }}" \ --secret-key "{{ minio_root_password }}" \ "redis://:{{ redis_password }}@{{ storage_info.redis_url }}/0" \ "{{ jfs_name | default('hybrid-cloud-jfs') }}"-
--storage s3: MinIO S3 호환 스토리지 백엔드 지정 -
--bucket "{{ storage_info.s3_url }}/jfs": S3 버킷 경로 설정 -
--access-key "{{ minio_root_user }}": MinIO 접근 키 주입 -
--secret-key "{{ minio_root_password }}": MinIO 비밀 키 주입 -
redis://:{{ redis_password }}@{{ storage_info.redis_url }}/0: Redis 메타데이터 엔진 연결 URI (DB 0) -
{{ jfs_name | default('hybrid-cloud-jfs') }}: 파일 시스템 이름 (기본값: hybrid-cloud-jfs)
-
-
failed_when조건을 상세화하여, "already exists" 메시지가 포함된 에러는 정상으로 간주함으로써 기존 파일 시스템을 보호하고 멱등성을 유지합니다. -
no_log: true설정을 통해 Ansible 실행 로그에 민감한 S3/Redis 인증 정보가 노출되는 것을 차단합니다. -
run_once: true옵션을 사용하여 클러스터 전체에서 단 한 번만 포맷 명령이 실행되도록 보장함으로써 메타데이터의 무결성을 지킵니다.
-
/etc/fuse.conf에allow_other옵션을 주입하여 root 이외의 일반 사용자나 애플리케이션 프로세스가 마운트된 볼륨에 접근할 수 있도록 설정합니다. -
해당 설정은 다중 사용자 환경에서 JuiceFS를 애플리케이션 스토리지로 활용하기 위한 필수 전제 조건입니다.
-
roles/juicefs/templates/juicefs-mount.service.j2를 통해 각 노드별 환경 변수와 경로가 주입된 유닛 파일을 생성합니다. -
생성된 서비스를 시스템에 등록 및 시작하여, 노드 재부팅 시에도 JuiceFS 볼륨이 자동으로 마운트되도록 구성합니다.
-
Tailscale 서비스에 대한 의존성 (
After/Wants)을 부여하여 네트워크 준비 전 마운트 시도로 인한 오류를 원천 차단합니다. -
Ansible의 Ternary Operator 로직을 통해 템플릿이나 FUSE 설정이 변경될 때만 서비스를 선별적으로 재시작함으로써 운영 효율성을 높입니다.
-
timeout 5 df -h명령을 사용하여 스토리지 응답 지연 시 태스크가 무한 대기하는 현상을 방지하고, 안정적으로 마운트 여부를 확정합니다. -
마운트 서비스 시작 후 물리적 시간이 필요하므로, 최대 5회 재시도 (5초 간격)를 설정하여 안정적인 검증을 수행합니다.
-
FUSE 마운트 프로세스 중에 소유권이 root로 변경되는 시스템적 현상을 방지하기 위해, Ansible
file모듈을 활용하여 마운트 완료 후에도ansible_user소유권과 권한 (0770)을 재강제합니다. -
이는 마운트 전후 양쪽에서 소유권을 검증하는 이중화 전략으로 권한 충돌을 완전히 차단합니다.
MDI (Model-Driven Infrastructure) 설계에 따라 분류된 그룹 (
control_plane,worker)과 전략 (init,join)을 바탕으로, 하이브리드 환경에 최적화된 K3s 클러스터를 선언적으로 구축합니다.
물리적 위치 (On_Premise/AWS)에 관계없이 모든 노드는 Tailscale Mesh 네트워크를 통해 하나의 논리적 서브넷으로 통합됩니다. 이를 위해 모든 부트스트래핑 과정에서 다음 설정을 강제합니다.
-
고정 엔드포인트 (
ts_ip): 리졸버가 추출한tailscale0인터페이스의 IP를 모든 노드의 대표 주소로 활용합니다. -
통신 인터페이스 표준화: K3s의 내부 통신 및 Flannel CNI가 물리 인터페이스가 아닌
tailscale0를 바라보도록 설정하여 하이브리드 환경에서의 통신 안정성을 확보합니다. -
노드 역할별 최적화 템플릿: Seed, Join, Worker 등 각 역할에 맞는 Jinja2 템플릿을 동적으로 렌더링합니다.
-
Control Plane에는 API 인증서에 Tailscale IP를 바인딩하는
tls-san추가 -
Worker 노드에는 불필요한 Kubeconfig 생성 옵션 제거
-
-
핵심 구성 플래그: 모든 노드의 설정 파일에는 네트워크 경계를 허물고 보안을 강화하기 위한 다음 파라미터가 공통으로 포함됩니다.
# K3s 노드 통신 및 인터페이스 고정 설정 node-ip: "{{ ts_ip }}" node-external-ip: "{{ ts_ip }}" flannel-iface: "tailscale0"
1. Playbook for Controller: roles/k3s-controller/tasks/main.yml
-
Cluster Seed (
k8s_init):roles/k3s-controller/templates/k3s-init.yaml.j2-
AWS (
t4g-seed)를 클러스터의 기점으로 삼아 내장etcd를 초기화합니다. -
--cluster-init플래그를 통해 고가용성 아키텍처의 초석을 다지며, 부트스트래핑 성공 후 생성된 Cluster Token을 안전하게 추출 (slurp)하여 전역 변수화합니다.
-
-
HA Expansion (
k8s_join):roles/k3s-controller/templates/k3s-join.yaml.j2-
AWS (
t3-*,t4g-*)/ On-Premise (site-a-node) 등 추가 컨트롤 플레인 노드를 시드 노드에 합류시킵니다. -
etcdQuorum 보호를 위해throttle: 1전략을 채택하여, 추가 컨트롤 플레인 노드들이 순차적으로 안전하게 합류하도록 제어합니다. -
hostvars를 통해 공유된 시드 노드의 Tailscale IP와 Token을 활용하여 물리적 위치에 관계없이 논리적 제어 계층을 확장합니다.
-
2. Playbook for Worker: roles/k3s-worker/tasks/main.yml
-
MDI 기반 컴퓨팅 확장:
roles/k3s-worker/templates/k3s-worker.yaml.j2-
컨트롤 플레인 구축 직후, Seed 노드의 Tailscale IP와 보안 토큰을
hostvars에서 동적으로 참조하여 연산 전용 노드를 클러스터에 안전하게 가입시킵니다. -
모든 에이전트 옵션을 Jinja2 템플릿으로 관리하여, 설정 변경 시 시스템 서비스가 이를 즉시 감지하고 재시작되도록 멱등성을 확보했습니다.
-
-
Agent-Only Mode
-
k3s agent명령어를 통해 제어 오버헤드를 최소화하고 순수 연산 자원만 확보합니다. -
모든 Pod 간 통신은
tailscale0인터페이스를 강제하여 인터넷 망 노출 없이 안전한 하이브리드 데이터 플레인을 형성합니다. -
ansible_facts기반의 자원 예약 계산식을 통해 하이브리드 노드별 (AWS/On-Premise) 맞춤형 하드닝을 수행합니다.
-
Step 6. Master Orchestration Entrypoint: site.yml
개별적으로 작성된 모든 Playbook을 논리적 선후 관계에 따라 순차 실행하여, 하이브리드 클라우드의 완전한 부트스트래핑을 달성하는 단일 진입점입니다.
-
common과tailscale플레이북을 동시 실행하여 모든 노드를 메시 네트워크로 묶습니다. -
resolver의has_storage메타데이터에 따라 JuiceFS 마운트 여부를 동적으로 결정하며 기초 구성을 완료합니다.
-
k3s-controller플레이북을 실행합니다. 이 단계의 핵심은 데이터의 흐름입니다. -
k8s_init노드가 생성한 클러스터 기점 (Seed) 정보를hostvars로 추출하여, 지연 실행되는k8s_join노드들에게 안전하게 전달합니다.
- 완성된 Control Plane 위로 Worker 노드들을 정렬시킵니다.