21장. AI 거버넌스와 보안, 온프레미스 인프라
CI/CD 자동화
은행 본사가 새 규칙을 모든 창구에 내릴 때는, 점검표를 통과해야 배포 버튼이 눌립니다. CI/CD 는 코드를 고쳐 올릴 때마다 시험(CI)을 자동으로 돌리고, 통과하면 배포(CD)까지 이어서 하는 방식입니다. 19장의 품질 게이트와 이 장의 보안 시험을 여기에 넣습니다.
1번에서 코드나 프롬프트를 저장소에 올리면, 2번 빠른 시험(문법, 단위 시험), 3번 보안 점검(공격 묶음, 개인정보, 접근 제어), 4번 품질 게이트(평가 묶음, 19~20차시)가 차례로 돕니다. 하나라도 실패하면 5번 배포는 일어나지 않습니다. 점검표가 2~4번, 배포 버튼이 5번입니다.
파이프라인 파일의 꼴
아래는 이 PC 에서 실행하지 않은 안내 코드입니다. 깃허브의 GitHub Actions 꼴로, 사내 GPU 서버에 둔 실행기(self-hosted runner)에서 점검 스크립트를 돌립니다. 사내 서버에서는 GitLab CI, Jenkins 같은 도구로 같은 일을 합니다.
# .github/workflows/llm-check.yml
name: llm-check
on: [push]
jobs:
check:
runs-on: [self-hosted, gpu] # 사내 GPU 서버의 실행기(Ollama 가 떠 있다)
steps:
- uses: actions/checkout@v4
- run: pip install -r requirements.txt
- run: python out_ci_check.py # 실패하면 종료 코드 1 → 배포 단계로 넘어가지 않는다
deploy:
needs: check
runs-on: [self-hosted, gpu]
steps:
- run: docker compose up -d --build점검이 실패했을 때의 예상 출력(실행하지 않고 유추한 예시)입니다.
check ✗ Process completed with exit code 1.
deploy ⊘ Skipped (needs: check)단계를 차례로 돌리기
def pipeline(stages):
"""단계를 차례로 돌리고, 실패하면 거기서 멈춘다"""
for name, ok in stages:
print(("통과" if ok else "실패"), name)
if not ok:
print("→ 배포하지 않습니다")
return 1 # 종료 코드 1: CI 도구가 실패로 본다
print("→ 배포합니다")
return 0
print("종료 코드", pipeline([("빠른 시험", True), ("보안 점검", False), ("품질 게이트", True)]))코드에 나오는 함수
| 코드 | 쉬운 뜻 |
|---|---|
for name, ok in stages | (단계 이름, 통과 여부) 짝을 하나씩 꺼낸다 |
return 1 | 실패하면 1 을 돌려주고 바로 끝낸다. 뒤의 단계는 돌지 않는다 |
return 0 | 모두 통과하면 0 을 돌려준다 |
| 종료 코드(exit code) | 프로그램이 끝나며 남기는 숫자다. 0 은 성공, 0 이 아니면 실패로 보는 것이 약속이다. CI 도구는 이 숫자로 다음 단계를 할지 정한다 |
이 코드가 뜻하는 것
공항의 탑승 절차와 같습니다. 발권, 보안 검색, 탑승구를 차례로 지나고, 보안 검색에서 걸리면 탑승구까지 가지 않습니다. 여기서 발권, 보안 검색, 탑승구가 세 단계이고, 비행기에 타는 일이 배포입니다.
| 줄 | 하는 일 | 쉽게 말하면 |
|---|---|---|
for name, ok in stages: | 단계를 차례로 본다 | 창구를 하나씩 지나기 |
if not ok: ... return 1 | 실패하면 멈추고 1 을 돌려준다 | 검색대에서 걸려 돌아가기 |
print("→ 배포합니다"), return 0 | 모두 통과하면 배포 | 비행기에 타기 |
결과에 "품질 게이트" 줄이 없습니다. 보안 점검에서 멈췄기 때문입니다. 마지막 줄의 종료 코드 1 이 CI 도구에 실패를 알립니다.
작게 줄여서 눈으로 보기
종료 코드가 실제로 다른 프로그램에 어떻게 전해지는지, 파이썬을 하나 더 띄워 봅니다. CI 도구가 점검 스크립트를 돌릴 때도 이렇게 숫자를 받습니다.
import subprocess
import sys
# 파이썬을 하나 더 띄워 종료 코드 0 과 1 로 끝내 본다
for code in [0, 1]:
r = subprocess.run([sys.executable, "-c", f"import sys; sys.exit({code})"])
print(f"sys.exit({code}) → returncode {r.returncode}")sys.exit(0) → returncode 0
sys.exit(1) → returncode 1| 결과 | 읽는 법 |
|---|---|
sys.exit(0) → returncode 0 | 0 으로 끝낸 프로그램의 종료 코드를 바깥에서 0 으로 받았다. 성공이다 |
sys.exit(1) → returncode 1 | 1 로 끝내면 1 을 받는다. CI 도구는 이것을 실패로 보고 다음 단계를 건너뛴다 |
subprocess.run 은 subprocess(하위 프로세스), 곧 프로그램 안에서 다른 프로그램을 띄워 끝날 때까지 기다립니다. sys.executable 은 지금 쓰는 파이썬 실행 파일의 경로이고, -c 는 command(명령)로 뒤의 글을 코드로 실행합니다. sys.exit(n) 은 종료 코드 n 을 남기고 끝내며, returncode 가 그 숫자입니다.
주의할 점
- LLM 의 답은 실행마다 조금씩 달라 시험이 가끔 들쭉날쭉합니다. 문턱에 여유를 두고, 실패하면 실패한 문항을 기록에 남겨 사람이 봅니다.
- CI 에서 쓰는 비밀(API 키, 서버 주소)은 저장소에 적지 않고 CI 도구의 비밀 저장소에 둡니다.