시리즈 : GitLab + Terraform + Ansible 으로 VM 프로비저닝 자동화하기
1. [Rocky Linux 기반 GitLab Docker 설치하기]
2. [Terraform 기초 - Docker Provider로 배우기]
3.GitLab Runner 설치 및 Terraform 파이프라인 구성
4. GitLab CI/CD로 VM 자동 생성 실전 (예정)
5. [Terraform 디스크 문제와 xe 명령어 전환] (예정)
6. [GitHub vs GitLab CI/CD 실전 비교] (예정)
🔹 들어가며
이전 글에서 GitLab을 Docker로 구축하고, Terraform 기초를 Docker Provider로 학습했습니다. 이번에는 본격적으로 GitLab Runner를 설치하고 CI/CD 파이프라인을 구성해보았는데요.
Runner 설치부터 .gitlab-ci.yml 파이프라인 작성까지 다루고, 다음 편에서 실제로 VM을 생성하는 과정을 다루겠습니다.
이번 편에서 다룰 내용
- Terraform XO Provider 네트워크 설정 문제 해결 - NIC명 인식실패 이슈 트러블슈팅
- GitLab Runner 설치 및 등록
- GitLab 프로젝트 생성
- Terraform 코드 CI/CD용으로 수정
- 3단계 파이프라인 구성 (validate → plan → apply)
🔹 아키텍처
┌─────────────────────────────────────────────────────────────────────────┐
│ 작업 흐름 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ [개발자] │
│ │ │
│ │ 1. git push (Terraform 코드) │
│ ▼ │
│ [GitLab] │
│ │ http://gitlab.company.local │
│ │ │
│ │ 2. Pipeline 트리거 │
│ ▼ │
│ [GitLab Runner] │
│ │ shell executor │
│ │ │
│ │ 3. terraform init/validate/plan/apply │
│ ▼ │
│ [Xen Orchestra API] │
│ │ wss://[XO_SERVER_IP] │
│ │ │
│ │ 4. VM 생성 요청 │
│ ▼ │
│ [XCP-NG] │
│ │ │
│ ▼ │
│ [VM 생성 완료] ← 다음 편에서 다룰 예정 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
🔹 준비 환경
이미 구축된 구성 요소
| Docker | 29.1.5 | 실행 중 | - |
| GitLab CE | latest | 실행 중 (Docker) | #19 |
| Terraform | 1.14.3 | 설치됨 | #20 |
| XO Provider | 0.26.1 | 설치됨 | - |
작업 서버
- GitLab/Runner 서버: Rocky Linux 9.7
- XCP-NG 호스트: 가상화 서버(LNX)
- XO 서버: Xen Orchestra 관리 서버
🔹 Step 1: Terraform XO Provider 네트워크 트러블슈팅
이슈 상황
Terraform 코드를 작성하고 terraform plan을 실행했는데 NIC 관련 에러가 발생했습니다.
$ terraform plan
Error: Invalid data resource name
on main.tf line 12, in data "xenorchestra_network" "Pool-wide network associated with eth0":
12: data "xenorchestra_network" "Pool-wide network associated with eth0" {
A name must start with a letter or underscore and may contain only letters,
digits, underscores, and dashes.
# ❌ 잘못된 코드 - 리소스 이름에 공백과 특수문자 사용
data "xenorchestra_network" "Pool-wide network associated with eth0" {
name_label = "eth0" # 잘못된 name_label
}
처음에는 XO의 네트워크 이름을 그대로 리소스 이름으로 사용하였는데, 이것이 Terraform의 네이밍 규칙에 맞지 않아 에러가 발생한 것이었습니다.
분석
Terraform 리소스 이름과 XO 네트워크 name_label의 역할을 혼동했습니다.
| 구분 | 위치 | 용도 | 규칙 |
| 리소스 이름 | 두 번째 따옴표 "net" | Terraform 코드 내부에서 참조할 때 사용 | 영문, 숫자, _, - 만 허용 |
| name_label | 속성 값 | XO API에서 실제 검색할 네트워크 이름 | 제한 없음 (공백, 특수문자 가능) |
해결 방법
XO 웹 UI에서 실제 네트워크 이름을 확인했습니다.
XO에서 네트워크 이름 확인하는 방법:
- XO 웹 UI 접속
- Home → Pools → 해당 Pool 선택
- Network 탭 확인
- 네트워크 이름: Pool-wide network associated with eth0
수정된 코드:
- "MY_NETWORK_ID" = Terraform 코드에서 이 네트워크를 참조할 때 사용하는 변수명 (마음대로 정할 수 있음)
- "실제 XO에 있는 네트워크 이름" = XO에 실제로 존재하는 네트워크의 정확한 이름 (반드시 일치해야 함)
# ✅ 올바른 코드
data "xenorchestra_network" "net" {
name_label = "Pool-wide network associated with eth0"
}
정리:
- "net" = Terraform 내부 참조명
- "Pool-wide network associated with eth0" = XO의 실제 네트워크 이름
수정 후 테스트
$ terraform plan
data.xenorchestra_network.net: Reading...
data.xenorchestra_pool.pool: Reading...
data.xenorchestra_template.rocky_template: Reading...
data.xenorchestra_network.net: Read complete after 0s [id=fd11b7be-e45e-1169-08c3-8eca6fbacfd9]
data.xenorchestra_pool.pool: Read complete after 0s [id=a4a6e4f7-f1d7-b679-1a84-6976f4408910]
data.xenorchestra_template.rocky_template: Read complete after 0s [id=161d9507-c826-fa65-bf9a-0a6fa755a4a3]
Terraform will perform the following actions:
# xenorchestra_vm.new_rocky_vm will be created
+ resource "xenorchestra_vm" "new_rocky_vm" {
+ cpus = 2
+ memory_max = 4294967296
+ name_label = "VM-Provisioned-by-Terraform"
...
}
Plan: 1 to add, 0 to change, 0 to destroy.
성공! 👍
🔹 Step 2: GitLab Runner 설치
GitLab Runner란?
GitLab CI/CD 파이프라인을 실행하는 에이전트
역할:
- GitLab에서 파이프라인이 트리거되면 Runner가 작업을 수행
- 테스트, 빌드, 배포 등 자동화 작업 실행
- 여러 Executor 타입 지원 (ex. shell, docker, kubernetes 등)
우리가 사용할 구성:
- Executor: shell (호스트에 설치된 Terraform 직접 사용)
- 장점: 간단하고 빠름
GitLab Runner 저장소 추가
$ curl -L 'https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.rpm.sh' | sudo bash
Detected operating system as rocky/9.
Checking for curl...
Detected curl...
Downloading repository file: https://packages.gitlab.com/install/repositories/runner/gitlab-runner/config_file.repo?os=rocky&dist=9&source=script
done.
Installing yum-utils...
Generating yum cache for runner_gitlab-runner...
Generating yum cache for runner_gitlab-runner-source...
The repository is setup! You can now install packages.
GitLab Runner 설치 패키지 및 버전 조회
설치 중:
gitlab-runner x86_64 18.8.0-1 runner_gitlab-runner 26 M
gitlab-runner-helper-images noarch 18.8.0-1 runner_gitlab-runner 520 M
트랜잭션 요약
================================================================================
설치 2 패키지
다운로드 크기: 546 M
설치 크기: 2.0 G
완료되었습니다!
$ gitlab-runner --version
Version: 18.8.0
Git revision: 9ffb4aa0
Git branch: 18-8-stable
GO version: go1.25.3
Built: 2026-01-15T15:55:25Z
OS/Arch: linux/amd64
GitLab에서 Runner 토큰 발급
GitLab Runner를 GitLab 인스턴스에 등록하기 위해 토큰을 발급해야 했습니다.
참고로, GitHub Runner 설치 후 러너 접근을 위해 토큰을 발급하는 것과 동일한 과정입니다.

발급 절차:
- GitLab 웹 UI 접속
- Admin Area (렌치 아이콘) 클릭
- CI/CD → Runners 메뉴
- New instance runner 클릭
- 설정:
- Platform: Linux
- Tags: lnx_test_gitlab_runner (파이프라인에서 이 Runner 지정 시 사용)
- Run untagged jobs: 체크박스 체크 완료
- Create runner 클릭
- 화면에 표시된 토큰 복사 (glrt-... 형식)
Tags 설정의 의미:
- .gitlab-ci.yml에서 tags: [lnx_test_gitlab_runner]로 이 Runner를 지정 가능
- 여러 Runner가 있을 때 특정 Runner로 작업을 라우팅할 수 있음
Runner 등록
$ sudo gitlab-runner register \
--non-interactive \
--url http://[GITLAB_IP] \
--token [토큰 값] \
--executor shell \
--description 'terraform-runner'
파라미터 설명:
- --non-interactive: 대화형 입력 없이 자동 등록
- --url: GitLab 인스턴스 URL
- --token: 발급받은 Runner 토큰
- --executor shell: Shell Executor 사용
- --description: Runner 설명 (GitLab UI에서 표시됨)
결과:
Runtime platform arch=amd64 os=linux pid=123456 revision=9ffb4aa0 version=18.8.0
Running in system-mode.
Verifying runner... is valid runner=YuV3aAd_o
Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!
Configuration (with the authentication token) was saved in "/etc/gitlab-runner/config.toml"
Runner 상태 확인
$ sudo gitlab-runner status
gitlab-runner: Service is running
$ sudo gitlab-runner list
Runtime platform arch=amd64 os=linux
Listing configured runners ConfigFile=/etc/gitlab-runner/config.toml
terraform-runner Executor=shell Token=glrt-YuV3aAd_o... URL=http://[GITLAB_IP]
설정 파일 위치:
- /etc/gitlab-runner/config.toml - Runner 설정 저장
주요 설정 확인:
concurrent = 1
check_interval = 0
[session_server]
session_timeout = 1800
[[runners]]
name = "terraform-runner"
url = "http://[GITLAB_IP]"
id = 1
token = "[토큰 값]"
token_obtained_at = 2026-01-27T06:00:00Z
token_expires_at = 0001-01-01T00:00:00Z
executor = "shell"
[runners.cache]
MaxUploadedArchiveSize = 0
주요 설정 항목:
- concurrent = 1: 동시에 실행할 수 있는 Job 수
- executor = "shell": Shell Executor 사용
🔹 Step 3: GitLab 프로젝트 생성
GitLab 웹 UI에서 프로젝트 생성
- GitLab 접속 (root 계정 로그인)
- New project 버튼 클릭
- Create blank project 선택
- 프로젝트 설정:
- Project name: terraform_integration_test
- Project URL: http://[GITLAB_IP]/root/terraform_integration_test
- Visibility Level: Private (비공개)
- Initialize repository with a README: ❌ 체크 해제
- Create project 클릭
프로젝트가 생성 완료!
🔹 Step 4: Terraform 코드 CI/CD용으로 수정
비밀번호 하드코딩 문제
기존 Terraform 코드에서는 XO 비밀번호를 직접 코드에 작성했습니다.
# ❌ 나쁜 예 - 비밀번호 하드코딩
provider "xenorchestra" {
url = "wss://[XO_SERVER_IP]"
username = "root"
password = "[실제 패스워드 값]" # 패스워드 값 그대로 저장, 보안상 위험
insecure = true
}
문제점:
- Git 저장소에 비밀번호가 노출됨
- 팀원들이 모두 비밀번호를 볼 수 있음
- 비밀번호 변경 시 코드 수정 필요
해결 방법: GitLab CI/CD Variables 활용
비밀번호를 코드에서 분리하고 GitLab CI/CD Variables로 관리할 수 있습니다.

디렉토리 구조 정리
# Terraform 작업 디렉토리 생성
$ mkdir -p /home/terraform
$ cd /home/terraform
provider.tf 작성
terraform {
required_providers {
xenorchestra = {
source = "terra-farm/xenorchestra"
}
}
}
# ===== 변수 정의 =====
variable "xo_url" {
description = "XO WebSocket URL"
default = "wss://[XO_SERVER_IP]"
}
variable "xo_username" {
description = "XO username"
default = "admin"
}
variable "xo_password" {
description = "XO password" # TF_VAR_xo_password 와 매핑
sensitive = true
# default 값 없음 - GitLab CI/CD Variables에서 주입됨
}
# ===== Provider 설정 =====
provider "xenorchestra" {
url = var.xo_url
username = var.xo_username
password = var.xo_password # 변수 참조
insecure = true
}
핵심 포인트:
- variable "xo_password"에 default 값을 지정하지 않음
- sensitive = true로 로그에 노출 방지
- GitLab CI/CD에서 TF_VAR_xo_password 환경변수로 자동 주입됨
Terraform 환경변수 규칙:
- TF_VAR_ 접두사가 붙은 환경변수는 Terraform이 자동으로 인식
- TF_VAR_xo_password → var.xo_password로 자동 매핑
main.tf 작성
# ===== 1. 템플릿 조회 =====
data "xenorchestra_template" "rocky_template" {
name_label = "Rocky_9_template"
}
# ===== 2. Pool 정보 조회 =====
data "xenorchestra_pool" "pool" {
name_label = "xcp-ng-pool"
}
# ===== 3. 네트워크 정보 조회 =====
data "xenorchestra_network" "net" {
name_label = "Pool-wide network associated with eth0"
}
# ===== 4. VM 생성 =====
resource "xenorchestra_vm" "new_rocky_vm" {
name_label = "VM-Provisioned-by-Terraform"
name_description = "Terraform을 통해 Rocky_9_template에서 복제됨"
template = data.xenorchestra_template.rocky_template.id
cpus = 2
memory_max = 4294967296 # 4GB
network {
network_id = data.xenorchestra_network.net.id
}
disk {
name_label = "VM-Provisioned-by-Terraform-Disk"
sr_id = "[STORAGE_REPOSITORY_ID]" # 실제 SR ID로 변경 필요
size = 21474836480 # 20GB
}
high_availability = "best-effort"
}
SR ID 확인 방법:
# XO CLI로 확인
$ xe sr-list
# 또는 XO 웹 UI에서 확인
# Home → Storage → 해당 Storage 클릭 → UUID 복사
.gitignore 생성
민감정보가 저장된 파일(개인정보 보관이나 인증서(*.cer, *.pem 등)은 Git에 커밋하지 않도록 설정해야 합니다.
$ cat > .gitignore << 'EOF'
# Terraform
.terraform/
*.tfstate
*.tfstate.backup
*.tfvars
.terraform.lock.hcl
# Backup
backup_*/
# Secrets
*.pem
*.key
EOF
제외할 파일들:
- .terraform/: Provider 플러그인 (용량 큼, 재생성 가능)
- *.tfstate: 현재 인프라 상태 (민감 정보 포함 가능)
- *.tfvars: 변수 파일 (비밀번호 포함 가능)
- .terraform.lock.hcl: Provider 버전 잠금 파일
🔹 Step 5: CI/CD 파이프라인 구성
.gitlab-ci.yml 작성
GitLab CI/CD 파이프라인을 validate → plan → apply 3단계로 구성하였습니다.
stages:
- validate
- plan
- apply
variables:
TF_ROOT: ${CI_PROJECT_DIR}
default:
tags:
- lnx_test_gitlab_runner # Runner 태그 지정
before_script:
- cd ${TF_ROOT}
- terraform version
# ===== 1단계: 검증 =====
validate:
stage: validate
script:
- terraform init
- terraform validate
only:
- merge_requests
- main
# ===== 2단계: 실행 계획 =====
plan:
stage: plan
script:
- terraform init
- terraform plan -out=tfplan
artifacts:
paths:
- tfplan
expire_in: 1 hour
only:
- merge_requests
- main
# ===== 3단계: 배포 (수동 승인) =====
apply:
stage: apply
script:
- terraform init
- terraform apply -auto-approve tfplan
dependencies:
- plan
when: manual # 수동 승인 필요
only:
- main
파이프라인 흐름 설명
git push
│
▼
┌─────────────┐
│ validate │ ← 자동 실행
│ - init │ Terraform 문법 검증
│ - validate │
└──────┬──────┘
│ ✅ 성공 시
▼
┌─────────────┐
│ plan │ ← 자동 실행
│ - init │ 변경사항 미리보기
│ - plan │ tfplan 파일 생성
│ (artifact) │
└──────┬──────┘
│ ✅ 성공 시
▼
┌─────────────┐
│ apply │ ← 수동 승인 필요 (▶️ 버튼 클릭)
│ - init │ 실제 VM 생성
│ - apply │
└─────────────┘
파이프라인 변수 설명
variables:
TF_ROOT: ${CI_PROJECT_DIR}
- CI_PROJECT_DIR: GitLab이 자동으로 제공하는 변수 (프로젝트 루트 경로)
- TF_ROOT: Terraform 작업 디렉토리 (여기서는 프로젝트 루트와 동일)
before_script 설명
before_script:
- cd ${TF_ROOT}
- terraform version
- 모든 Job이 실행되기 전에 자동 실행
- Terraform 작업 디렉토리로 이동
- Terraform 버전 확인 (로그에 기록)
🔹 최종 디렉토리 구조
/home/terraform/
├── .gitignore # Git 제외 파일 목록
├── .gitlab-ci.yml # CI/CD 파이프라인 정의
├── main.tf # VM 리소스 정의
└── provider.tf # Provider 및 변수 설정
🔹 마치며
이번 글에서는 GitLab Runner를 설치하고 CI/CD 파이프라인을 구성해보았습니다.
완료한 작업:
- ✅ Terraform XO Provider 네트워크 설정 문제 해결
- ✅ GitLab Runner 설치 및 등록 (Shell Executor)
- ✅ GitLab 프로젝트 생성
- ✅ Terraform 코드 CI/CD용으로 수정 (비밀번호 분리)
- ✅ 3단계 파이프라인 구성 (validate → plan → apply)
핵심 개념:
- Terraform 리소스 이름 vs XO name_label 구분
- CI/CD Variables를 통한 민감정보 관리
- when: manual로 안전한 배포 프로세스(리소스 배포 승인 로직) 구현
다음 편에서는 이렇게 구성한 파이프라인을 실제로 GitLab에 push하고, VM을 자동으로 생성하는 과정을 다뤄보겠습니다!
이전 글 : [Terraform 기초 다시 시작하기 - Docker Provider 컨테이너 자동화]
다음글 : GitLab CI/CD로 VM 자동 생성 실전 (작성 예정)
'기술 공부 > DevOps' 카테고리의 다른 글
| GitLab CI/CD 파이프라인 기능별 분리 - 기능 별 동작 구성 (1) | 2026.02.07 |
|---|---|
| Terraform 기초 다시 시작하기 - Docker Provider 컨테이너 자동화 (0) | 2026.01.25 |
| Rocky Linux 기반 GitLab Docker 설치하기 (0) | 2026.01.24 |
| Windows에서 Git & GitHub 연동하기 - 실전 가이드 (0) | 2026.01.15 |
| VirtualBox NAT 네트워크 설정과 SSH 포트포워딩 트러블슈팅 (0) | 2025.12.25 |