← AI 엔지니어링 심화

▶ 배포·공유

Deployment & Sharing
실전 · 배포개념 14세션 7실습 2▶ 실무 따라하기

내 PC에서만 돌던 AI 도구를, 정해진 사람이 안전하게 쓰도록 '정식 공표'하는 과정 — 시범운영에서 대민 공개까지.

4컷으로 보기 — 시범운영 후 정식 시행 공표와 같다

배포·공유 4컷 설명 웹툰
  1. 11컷: 내 자리에서만 쓰는 시범 서식
  2. 22컷: 공표 준비! 결재선과 금고 열쇠
  3. 33컷: 정식 시행, 당직이 창구를 지킨다
  4. 44컷: 모든 민원 창구에서 안전하게 사용

왜 중요한가

아무리 잘 만든 AI 도구도 '내 노트북에서만 켜지면' 조직 자산이 아니라 개인 실험으로 끝난다. 배포는 '작동하는 코드'를 '남이 믿고 쓰는 서비스'로 승격시키는 관문이며, 이 관문이 부재하면 프로세스 죽음·키 유출·개인정보 유출·롤백 불가가 동시에 터진다. 이 플랫폼 자체가 systemd+Cloudflare Tunnel로 배포돼 있어 교육생이 쓰는 사이트를 그대로 실습 교보재로 삼는다.

행정 실무 비유

시범운영 후 정식 시행 공표와 같다. 부서 안에서 시범 서식을 돌려보는 것(로컬 실행)과, 그 서식을 관보에 공표해 모든 민원 창구가 쓰게 하는 것(배포)은 전혀 다른 절차다. 공표하려면 결재선(빌드·검수)을 통과하고, 시행일·소관부서(도메인·프로세스 관리)를 지정하고, 열람 권한(접근통제)을 정하고, 문제가 생기면 이전 서식으로 되돌릴 근거(롤백)를 남겨야 한다. 관인·직인은 금고에 따로 보관하듯(환경변수·비밀), 서비스는 항시 창구를 여는 당직 체계(프로세스 매니저)가 지킨다.

🖐 실무 따라하기 — 복붙해서 따라 하면 됩니다

상황 민원팀 김주무관이 만든 '민원 답변 초안 도우미'(질문을 넣으면 정중한 답변 초안을 뽑아주는 웹앱)가 지금까지는 본인 PC에서 npm run dev로 켤 때만 돌아 다른 팀원은 못 썼다. 이번 주부터 민원팀 5명이 청 내부에서만 접속해 함께 쓰도록 '정식 서비스'로 올려야 한다. 개인 API 키와 접속 주소를 안전하게 관리하고, 문제가 생기면 즉시 이전 버전으로 되돌릴 수 있어야 한다. (모든 도메인·포트·키는 합성 예시이며, 실제 값은 기관 승인 후 교체한다.)
목표 내 PC에서만 돌던 민원 답변 도우미를 빌드→환경변수→상주 서비스(systemd)→터널·접근통제→서버측 PII 재검증→롤백 리허설까지 갖춘 팀 공용 정식 서비스로 공표한다.
준비물 Node.js 22+와 Next.js 앱이 있는 실습 PC(리눅스/맥). 이 챕터의 1~4·6단계(빌드·systemd·터널·DNS·API 라우트 수정)는 IT담당/개발 경험자 대상 고난도 작업이다. 망분리·무설치 PC의 일반 담당자는 1~4단계를 건너뛰고 5단계의 무설치 대안(ChatGPT·Claude 웹 또는 이 플랫폼 /playground에 프롬프트 붙여넣기)만 따라오면 되고, 정식 서비스 공표는 IT담당에게 인계하면 된다. 실습 도메인·포트는 전부 명백한 예시값(example.go.kr / minwon.example.com / 포트 3999)이며 실제 기관 인프라와 분리돼 있다 — 실서비스 적용 시 기관 승인 후 교체한다.
  1. 1정본(빌드) 만들기 — dev 말고 '검수된 결과물'을 굳힌다

    앱 폴더에서 개발용 실행을 멈추고, 배포용 최적화 빌드를 생성한다. dev 모드는 코드가 바뀔 때마다 다시 도는 '작업 중' 상태라 여러 명이 쓰기 부적합하다. build는 한 번 검수해 굳힌 '정본'을 만든다. 명령 앞의 폴더 경로만 본인 앱 경로로 바꾼다. (무설치 PC라면 이 단계를 건너뛰고 5단계 무설치 대안으로.)

    터미널 명령
    cd /home/gdi-s2/dev/aiedu
    npm run build
    
    # (무설치 대안) systemd·터널을 못 쓰는 청 내 PC라면, 빌드 후 아래로 로컬 정본만 띄워
    # 같은 망의 팀원이 http://<내PC_IP>:3999 로 접속하게 한다(포트 3999는 예시값):
    #   npm run start -- -p 3999

    → 결과 수십 초~2분 뒤 'Compiled successfully' 와 라우트 목록(예: ○ / , ƒ /api/explain)이 표로 출력되고 마지막에 에러 없이 프롬프트로 복귀한다. .next 폴더가 갱신되면 정본 완성. 빨간 'Failed to compile'이 뜨면 그 파일을 고친 뒤 다시 build.

  2. 2비밀은 코드가 아니라 환경변수 파일로 — .env.local 주입

    API 키·관리자 비밀번호·세션 비밀키를 코드가 아니라 별도 파일에 넣고, 그 파일을 본인만 읽게 잠근다. 코드에 키를 박으면 공유·백업 과정에서 유출된다. 아래를 그대로 실행하되 값(합성 예시)만 실제 발급값으로 바꾼다. 도메인은 예시값 example.go.kr — 실제 기관 도메인은 승인 후 교체한다. 이 파일은 절대 깃/메일/메신저로 전송하지 않는다.

    터미널 명령
    cd /home/gdi-s2/dev/aiedu
    cp -n .env.example .env.local 2>/dev/null || true   # 예시 파일이 있으면만 복사
    
    # SESSION_SECRET용 긴 랜덤값을 먼저 만든다(아래 파일의 값과 교체):
    openssl rand -hex 32
    
    cat > .env.local <<'EOF'
    ANTHROPIC_API_KEY=sk-ant-실제발급키를여기에
    SESSION_SECRET=위_openssl_출력값으로_교체
    [email protected]
    ADMIN_PASSWORD=바꿀비밀번호
    ALLOWED_EMAIL_DOMAINS=example.go.kr
    EOF
    
    chmod 600 .env.local             # 본인만 읽기/쓰기
    ls -l .env.local

    → 결과 openssl 명령이 64자리 16진수 한 줄(예: 3b7e...9a1)을 출력 → 이 값을 SESSION_SECRET에 붙여넣는다. 마지막 ls 결과 권한이 '-rw-------'(600)이면 성공. ALLOWED_EMAIL_DOMAINS 덕분에 example.go.kr 메일만 가입/로그인 가능해진다(실제 도메인은 승인 후 교체).

  3. 3상주 서비스로 등록 — 재부팅·크래시에도 24시간 자동 재기동(systemd)

    터미널을 닫으면 앱이 꺼지는 dev와 달리, systemd 사용자 서비스로 등록하면 로그아웃·재부팅·오류로 죽어도 자동으로 다시 켜진다. 아래 파일을 만들고 등록·시작한다. node 경로는 반드시 which node로 먼저 확인해 ExecStart에 반영한다(아래 경로는 예시). 포트 3999도 예시값이다.

    터미널 명령
    which node    # 출력된 절대경로를 아래 ExecStart의 node 경로에 그대로 사용
    
    mkdir -p ~/.config/systemd/user
    cat > ~/.config/systemd/user/minwon-web.service <<'EOF'
    [Unit]
    Description=Minwon reply helper (Next.js on :3999)
    After=network.target
    
    [Service]
    Type=simple
    WorkingDirectory=/home/gdi-s2/dev/aiedu
    EnvironmentFile=-/home/gdi-s2/dev/aiedu/.env.local
    Environment=NODE_ENV=production
    ExecStart=/usr/bin/node /home/gdi-s2/dev/aiedu/node_modules/next/dist/bin/next start -p 3999
    Restart=always
    RestartSec=5
    
    [Install]
    WantedBy=default.target
    EOF
    
    loginctl enable-linger $USER          # 로그아웃 후에도 계속 돌게
    systemctl --user daemon-reload
    systemctl --user enable --now minwon-web
    systemctl --user status minwon-web --no-pager
    curl -s -o /dev/null -w "%{http_code}
    " http://localhost:3999

    → 결과 status 출력에 초록색 'active (running)'이 보이고, 마지막 curl이 '200'을 출력하면 서비스가 상주 실행 중. 이제 터미널을 닫아도 http://localhost:3999 이 계속 응답한다. 죽여도 5초 뒤 자동 재기동(Restart=always). 'status=203/EXEC'가 뜨면 ExecStart의 node 경로가 틀린 것 — which node 결과로 고친다.

  4. 4팀에 공개 + 접근통제 — 터널로 도메인 연결, 로그인으로 아무나 못 들어오게

    내부 3999 포트를 방화벽 구멍 없이 외부 도메인에 안전하게 연결(터널)하고, 앱 자체 로그인+허용 도메인으로 '정해진 사람만' 들어오게 막는다. 접근통제 없이 도메인만 열면 URL 아는 누구나 API 키를 대신 쓰게 된다. 아래 도메인 minwon.example.com은 예시값 — 실제 기관 도메인은 승인 후 교체하며, 실운영 중인 DNS/터널 네임스페이스는 절대 건드리지 않는다.

    터미널 명령
    # 터널 생성·도메인 연결 (cloudflared 로그인은 최초 1회: cloudflared tunnel login)
    cloudflared tunnel create minwon
    cloudflared tunnel route dns minwon minwon.example.com   # 예시 도메인
    
    # 위 create 출력의 Tunnel ID(UUID)를 확인:
    cloudflared tunnel list
    
    # 설정파일 작성(<UUID>를 방금 확인한 값으로 교체):
    cat > ~/.cloudflared/minwon.yml <<'EOF'
    tunnel: <UUID>
    credentials-file: /home/gdi-s2/.cloudflared/<UUID>.json
    ingress:
      - hostname: minwon.example.com
        service: http://localhost:3999
      - service: http_status:404
    EOF
    
    cloudflared --no-autoupdate tunnel --config ~/.cloudflared/minwon.yml run <UUID>
    
    # (무설치 대안) 터널을 못 쓰면 공개하지 말고 같은 사내망에서만: 팀원은 http://<내PC_IP>:3999 접속.
    # 접근통제는 앱 로그인(ADMIN_EMAIL/PASSWORD로 로그인 → 팀원은 example.go.kr 메일로 가입)으로 유지된다.

    → 결과 터널 로그가 'Registered tunnel connection ... connIndex=0~3' 4줄을 찍으면 연결 완료. 브라우저에서 https://minwon.example.com 접속 시 앱 로그인 화면이 뜨고, example.go.kr 이외 메일은 가입이 거부된다(ALLOWED_EMAIL_DOMAINS). 로그인해야 답변 생성 기능이 열린다 = 접근통제 확인. (실제 도메인은 기관 승인 후 교체.)

  5. 5서버측 PII 재검증 — 주민번호는 차단, 전화는 마스킹해 통과

    클라이언트(브라우저) 검사는 우회 가능하므로, 서버가 외부 AI API로 보내기 직전 개인정보를 한 번 더 잡는다. 이때 (1) 주민번호는 즉시 차단(400)하고, (2) 전화번호는 차단 대신 [전화]로 마스킹해 통과시킨다 — 실습 표준 합성 전화 '010-0000-0000'이 든 정상 요청까지 400으로 거부되는 오작동을 막기 위해서다. 날짜(2026-07-07)·접수번호(MW-2026-0001) 같은 하이픈 숫자열은 절대 걸리지 않도록 계좌·일반 숫자열 광역 패턴은 아예 쓰지 않는다. 아래 최소 코드를 API 라우트 맨 앞에 넣는다. (무설치 대안 — 코드 수정이 어렵다면) ChatGPT·Claude 웹 또는 이 플랫폼 /playground에 아래 '시스템 프롬프트'를 붙여넣어 같은 재검증을 사람이 아닌 모델에게 시킨다: 『너는 민원 답변 도우미의 안전필터다. 사용자 입력에 주민등록번호(6자리-7자리)가 있으면 답변을 생성하지 말고 "개인정보가 포함되어 처리할 수 없습니다"만 출력하라. 전화번호(010-xxxx-xxxx)가 있으면 [전화]로 가린 뒤 답변하라. 접수번호(MW-YYYY-NNNN)와 날짜(YYYY-MM-DD)는 개인정보가 아니므로 그대로 둔다.』

    코드
    // app/api/explain/route.ts 등 사용자 입력을 받는 API 핸들러 맨 앞에 추가
    // 주민번호 = 차단(400). 전화번호 = 마스킹 후 통과. 날짜/접수번호는 매칭하지 않음.
    const RRN = /(?<!\d)\d{6}-[1-4]\d{6}(?!\d)/;              // 주민번호: 880101-1234567
    const PHONE = /(?<!\d)01[016-9]-?\d{3,4}-?\d{4}(?!\d)/g;   // 휴대전화: 010-0000-0000
    
    function guardPII(text: string): { blocked?: Response; clean: string } {
      if (RRN.test(text)) {
        return {
          blocked: Response.json(
            { error: "주민등록번호 등 민감 개인정보가 포함되어 처리할 수 없습니다. 홍길동 / 010-0000-0000 처럼 가상값으로 바꿔 주세요." },
            { status: 400 }
          ),
          clean: text,
        };
      }
      // 전화번호는 차단하지 않고 [전화]로 마스킹해 외부 API로 보낸다
      return { clean: text.replace(PHONE, "[전화]") };
    }
    
    // 핸들러 안에서:
    //   const { blocked, clean } = guardPII(userInput);
    //   if (blocked) return blocked;
    //   // 이후 clean(마스킹된 텍스트)만 Anthropic API로 전송

    → 결과 저장 후 step1의 build를 다시 돌리고 systemctl --user restart minwon-web. 이제: (1) '880101-1234567' 입력은 400 에러+안내 문구로 차단된다. (2) '홍길동 010-0000-0000 민원 답변 써줘'는 서버가 전화만 [전화]로 가린 뒤 정상 생성한다(차단 아님). (3) 반례 검증 — '접수 2026-07-07 MW-2026-0001 관련 답변'처럼 날짜·접수번호만 든 입력은 아무것도 안 걸리고 정상 통과한다. 세 가지가 모두 맞으면 재검증 로직 완성.

  6. 6롤백 안전망 확인 — 되돌릴 수 있어야 '공표'다

    배포는 되돌릴 수 있을 때만 안전하다. 새 빌드를 넣기 전 직전 정본을 보관하고, 문제가 생기면 한 줄로 되돌리는 절차를 실제로 한 번 연습해 둔다. (IT담당 작업 — 무설치 담당자는 5단계 무설치 대안까지만 따라오면 된다.)

    터미널 명령
    # (1) 새로 빌드하기 전, 현재 잘 도는 정본을 백업
    cp -r /home/gdi-s2/dev/aiedu/.next /home/gdi-s2/dev/aiedu/.next.bak
    
    # (2) 새 빌드 후 문제가 생기면 → 즉시 롤백:
    rm -rf /home/gdi-s2/dev/aiedu/.next
    mv /home/gdi-s2/dev/aiedu/.next.bak /home/gdi-s2/dev/aiedu/.next
    systemctl --user restart minwon-web
    curl -s -o /dev/null -w "%{http_code}
    " http://localhost:3999
    
    # (3) 모니터링: 상태와 최근 로그로 오류 감시
    systemctl --user status minwon-web --no-pager
    journalctl --user -u minwon-web -n 30 --no-pager

    → 결과 롤백 후 curl이 다시 '200'을 반환하면 직전 정본으로 정상 복귀. journalctl 로그에 500/에러가 없고 정상 요청만 보이면 안정. 이 롤백을 미리 한 번 연습해 두면 실제 사고 때 30초 내 원복 가능 = '되돌릴 수 있는 배포' 완성.

✅ 완주하면 민원팀 5명이 https://minwon.example.com(또는 사내망 http://내PC_IP:3999, 모두 예시값)에 청 메일로 로그인해 함께 쓰는, 재부팅에도 자동 복구되고 주민번호는 서버가 차단·전화는 마스킹하며 30초 내 롤백 가능한 상주 AI 서비스. 실제 도메인·포트·키는 기관 승인 후 교체하며, 이 챕터의 인프라 작업은 IT담당/개발 경험자가 수행하고 일반 담당자는 5단계 무설치 대안까지 따라오면 된다. · 성공 판정: (1) systemctl --user status minwon-web가 'active (running)'. (2) curl -s -o /dev/null -w "%{http_code} " http://localhost:3999이 200. (3) 브라우저에서 도메인 접속 시 로그인 화면이 뜨고 허용 도메인(example.go.kr) 외 메일은 가입 거부. (4) '880101-1234567' 입력은 400으로 차단되고, '홍길동 010-0000-0000' 입력은 전화만 [전화]로 마스킹돼 정상 생성되며, '2026-07-07'·'MW-2026-0001' 같은 날짜·접수번호는 통과. (5) .next.bak로 교체·재시작 후 다시 200 → 롤백 성공. [안전] .env.local은 chmod 600으로 잠그고 깃·메일·메신저로 절대 전송 금지. 터널은 반드시 앱 로그인+ALLOWED_EMAIL_DOMAINS 접근통제를 켠 상태에서만 공개. 실습엔 홍길동/010-0000-0000/MW-2026-0001 합성값만 사용. 도메인·포트·키는 전부 예시값이므로 실운영 DNS/터널 네임스페이스는 건드리지 말고 실제 값은 기관 승인 후 교체.

스택 내 위치

env-setup(실습 환경 준비) 위에 얹히는 실전 모듈. 프롬프트~메타 5계층으로 '만든' 것을, harness-eng(운영 장치)·orchestration-eng(다구성요소)로 '엮은' 결과물을 실제 사용자에게 '내보내는' 마지막 관문. eval-eng(품질 게이트)가 통과시킨 것만 배포로 넘어온다.

핵심 개념 (14)

빌드
Build

개발용 소스코드를 실제 서비스가 구동할 최적화된 산출물로 변환하는 과정. Next.js에서는 `npm run build`가 페이지·API를 미리 컴파일해 `.next` 폴더를 만든다. 개발모드(`next dev`)는 매 요청마다 다시 짜지만, 배포는 미리 굳혀둔 결과물(`next start`)을 그대로 낸다.

💡 '초안 회람본'과 '결재 완료·인쇄된 정본'의 차이. 정본을 안 만들면 매번 즉석 작성이라 느리고 오류가 늘어난다.

환경변수 주입
Environment Variable Injection

API 키·비밀번호·도메인 설정처럼 코드마다 달라지고 유출되면 안 되는 값을, 코드 바깥(예: `.env.local` 파일)에 두고 실행 시점에 프로그램에 넣어주는 방식. 이 플랫폼은 `.env.local`(권한 600, git 제외)에 ANTHROPIC_API_KEY·SESSION_SECRET을 두고 systemd가 EnvironmentFile로 주입한다.

💡 관인을 문서 본문에 인쇄하지 않고 금고에 따로 보관하는 것과 같다. 키가 코드에 박히면 코드 공유=키 유출.

프로세스 매니저
Process Manager

서버 프로그램이 죽거나 서버가 재부팅돼도 자동으로 다시 켜주고, 로그·재시작을 관리하는 상주 관리자. 리눅스 표준은 systemd, Node 진영엔 pm2가 흔하다. 이 플랫폼은 systemd user 유닛 `aiedu-web.service`가 `next start -p 4500`을 `Restart=always`로 지킨다.

💡 창구 당직 체계. 담당자가 로그아웃하거나 서버가 꺼져도 서비스가 계속 열려 있어야 '상시 대민 서비스'다.

리버스 프록시
Reverse Proxy

바깥 요청(도메인:443)을 받아 뒤에서 도는 실제 앱(예: localhost:4500)으로 넘겨주는 중개 서버. HTTPS 종료·부하 분산·경로 분기를 담당한다. Nginx가 대표적이며, 이 플랫폼은 Cloudflare Tunnel이 그 역할을 대신한다.

💡 민원실 안내데스크. 방문객은 안내데스크(공개 주소)만 알고, 실제 담당 부서 자리번호(내부 포트)는 몰라도 된다.

도메인·DNS
Domain / DNS

사람이 외우는 이름(aiedu.gdiaxhub.com)을 실제 서버 위치로 이어주는 전화번호부. DNS 레코드(A는 IP로, CNAME은 다른 이름/터널로) 설정으로 연결한다. 이 플랫폼은 CNAME으로 도메인을 Cloudflare 터널에 연결했다.

💡 관보에 실릴 '소관 창구 주소'. 주소가 없으면 아무도 찾아올 수 없다.

터널
Tunnel

서버가 바깥으로 나가는 안전한 통로를 스스로 뚫어, 방화벽에 인바운드 포트를 열지 않고도 인터넷에서 접속되게 하는 방식. Cloudflare Tunnel은 서버에서 `cloudflared`가 클라우드로 연결을 걸고, 외부 요청은 그 통로를 타고 들어온다. 이 플랫폼의 실제 방식이다.

💡 청사 방화벽에 구멍을 뚫는 대신, 내부에서 승인된 전용 왕래선을 여는 것. 공인 IP·포트 개방 없이 공개할 수 있어 보안·설정 부담이 작다.

호스팅
Hosting

서비스를 올려둘 실행 장소. 관리형 PaaS(Vercel·Netlify 등, 알아서 빌드·배포), 자체 서버/사내 VM(직접 프로세스·프록시 관리), 사내 온프레미스(망분리 대응) 등이 있다. 선택은 인증 필요 여부·개인정보·망분리·비용으로 갈린다.

💡 청사 사무실을 임차할지(PaaS), 자체 청사를 운영할지(자체 서버)의 결정. 다루는 자료 민감도가 장소를 정한다.

정적 vs 서버 렌더링
Static vs Server Rendering

정적(static export)은 미리 만든 HTML 파일만 뿌려 서버 로직이 없다(빠르고 싸고 공격면이 작음). 서버 렌더링은 매 요청마다 서버가 응답을 만들어 로그인·DB·AI 호출 같은 동적 기능이 가능하다. 이 플랫폼은 인증·API 라우트·AI 해설이 있어 서버 구동(`next start`)을 택했다.

💡 공표된 고정 서식(정적)과, 민원인마다 값을 채워 발급하는 창구(서버)의 차이. 기능이 없으면 정적이 더 안전·저렴하다.

접근통제·인증
Access Control / Authentication

누가 서비스에 들어올 수 있는지(인증)와 무엇을 할 수 있는지(인가)를 통제하는 장치. 로그인 세션, 허용 이메일 도메인 화이트리스트, 관리자 분리 등이 있다. 이 플랫폼은 SESSION_SECRET 기반 세션과 ALLOWED_EMAIL_DOMAINS로 가입 도메인을 제한한다.

💡 열람·발급 권한 대장. 공개 게시물과 내부 결재문서를 같은 창구에 두면 안 된다.

롤백
Rollback

새 배포가 문제를 일으켰을 때 직전의 정상 버전으로 즉시 되돌리는 능력. git 커밋·태그로 버전을 고정하고, 문제 시 이전 커밋을 체크아웃해 재빌드·재시작한다. '되돌릴 수 있게 배포하는 것'이 배포 설계의 핵심이다.

💡 개정 서식이 현장 혼란을 부르면 이전 서식으로 즉시 환원하는 부칙. 되돌릴 근거(버전 이력)가 없으면 사고가 고착된다.

가동시간·모니터링
Uptime / Monitoring

서비스가 살아있는지(가동시간), 오류·지연·비용이 정상인지 지속 관찰하는 활동. 헬스체크 URL 주기 점검, systemd 상태·로그(journalctl) 확인, 외부 상태 감시가 기본이다. 이 플랫폼 계열은 systemd timer로 헬스체크·워치독을 두기도 한다.

💡 창구가 실제로 열려 있는지 순찰하는 당직 점검. '문제를 사용자보다 먼저 아는 것'이 신뢰의 조건이다.

CI/CD 개요
CI/CD (Overview)

코드를 올리면(push) 자동으로 검사(CI: 빌드·테스트·타입체크)하고, 통과하면 자동으로 배포(CD)하는 파이프라인. 수작업 배포의 실수·누락을 줄인다. 소규모는 '빌드→재시작' 스크립트 한 줄로 시작해 점진 도입한다.

💡 기안→합의→결재→시행을 자동 결재선으로 표준화한 것. 사람이 매번 손으로 하면 단계 누락·실수가 난다.

비밀 관리
Secrets Management

API 키·세션 비밀·비밀번호를 안전하게 저장·회전·주입하는 규율. 최소 원칙: 코드/깃에 절대 커밋 금지, 파일 권한 제한(600), 유출 시 즉시 재발급(회전), 환경별 분리. 이 플랫폼은 `.env.local`을 .gitignore + chmod 600으로 관리한다.

💡 관인·직인 관리대장. 유출은 곧 위조·오남용이므로 보관·교체 규율이 서비스보다 먼저다.

망분리 배포 대안
Air-gapped / Intranet Deployment

인터넷이 차단된 업무망(행정망)에서의 배포. 외부 PaaS·외부 LLM API·공개 터널을 쓸 수 없으므로, 사내 서버에 오프라인 설치(node_modules·모델 반입), 내부 도메인·사내 인증서, 온프레미스/로컬 LLM(예: Ollama)로 대체한다.

💡 행정망 현실. '클라우드에 올리면 끝'이 성립하지 않는 환경에서 어떻게 같은 서비스를 사내에 세우는가가 공공 배포의 진짜 난제다.

학습 목표

L1 입문
  • 로컬 실행과 배포의 차이를 '시범운영↔정식공표'로 설명하고, 배포에 반드시 딸려오는 5가지 관문(빌드·환경변수·프로세스·접근통제·롤백)을 말할 수 있다
  • 자신이 쓰는 aiedu 사이트가 systemd + Cloudflare Tunnel로 떠 있음을 이해하고, 공개 주소가 내부 포트로 이어지는 경로를 그림으로 그릴 수 있다
  • API 키를 코드에 넣으면 왜 위험한지, 환경변수·`.env` 파일·git 제외의 관계를 설명할 수 있다
L2 실무
  • Next.js 앱을 `npm run build`로 빌드하고 `next start -p <포트>`로 구동한 뒤, systemd user 유닛으로 상주화(자동 재시작·부팅 자동시작)할 수 있다
  • Cloudflare Tunnel(또는 동급 터널)로 로컬 포트를 사내/공개 도메인에 연결하고, DNS CNAME 연결을 검증할 수 있다
  • 정적 export와 서버 구동 중 무엇을 택할지 기능 요구(인증·DB·AI 호출)로 판단하고, 접근통제(세션·허용 도메인)·PII 서버 재검증을 배포 앱에 켤 수 있다
  • git 태그·커밋 기반으로 롤백 절차를 문서화하고 실제로 직전 버전으로 되돌릴 수 있다
L3 심화
  • 호스팅 선택 기준표(관리형 PaaS vs 자체서버 vs 망분리 온프레미스)를 개인정보·망분리·비용·인증 요구로 작성해 부서에 제안할 수 있다
  • 헬스체크·로그(journalctl)·비용·오류율을 포함한 최소 모니터링·롤백 런북(runbook)을 만들고, 배포 승인 체크리스트(공표 결재선)에 안전 게이트 통과를 필수 항목으로 넣을 수 있다
  • 인터넷 차단 행정망을 가정해 외부 API·공개 터널 없이 동일 서비스를 사내에 세우는 대안 아키텍처(오프라인 설치·내부 도메인·로컬 LLM)를 설계할 수 있다

세션 구성

회차주제시수내용산출물
1로컬 실행 vs 배포 — '시범운영에서 정식공표로'2h왜 '내 노트북에서 켜짐'이 서비스가 아닌가. 배포에 딸려오는 5관문(빌드·환경변수·프로세스·접근통제·롤백) 개관. 이 플랫폼 실물 해부: 공개주소(aiedu.gdiaxhub.com)→Cloudflare 터널→localhost:4500→systemd가 지키는 next start. 정적 vs 서버 렌더링의 갈림길과 이 플랫폼이 서버를 택한 이유(인증·API·AI). 아키텍처 다이어그램을 직접 손으로 그려본다.배포 5관문 요약표 + 자기 손으로 그린 '요청이 사용자→도메인→터널→앱'까지 가는 경로 다이어그램
2빌드와 환경변수 주입 — 정본 만들기와 관인 분리2h개발모드(next dev)와 정본(build→start)의 차이를 직접 재현. `npm run build`로 `.next` 산출물 생성, `NODE_ENV=production next start`로 구동. 비밀 관리 규율: API 키를 코드에 박았을 때의 유출 시나리오 시연 → `.env.local`로 분리 → .gitignore + chmod 600 → systemd EnvironmentFile 주입. 키 회전(재발급) 절차.빌드 성공 로그 + `.env.local` 기반으로 구동되는 로컬 서버(localhost:4500) + '비밀 관리 체크리스트'
3프로세스 관리 — 당직 체계 세우기(systemd·pm2)2h터미널을 닫으면 서비스가 죽는 문제. 상주화가 필요한 이유. systemd user 유닛 작성(WorkingDirectory·EnvironmentFile·ExecStart·Restart=always·WantedBy=default.target), `systemctl --user enable/start/status`, 로그 확인 `journalctl --user -u`. pm2와의 비교(언제 무엇을). 재부팅 생존(linger) 개념.자기 앱을 지키는 `*-web.service` 유닛 파일 + enable된 상태 스크린샷 + 재시작 1회 실증
4공개 통로 — 리버스 프록시·도메인·터널(이 플랫폼 방식)2.5h바깥 요청을 내부 앱으로 잇는 3방식: Nginx 리버스 프록시 / 클라우드 PaaS / 터널. Cloudflare Tunnel 실습: cloudflared 설치, 터널 생성, config(ingress: hostname→localhost:4500), DNS CNAME 연결. 이 플랫폼이 겪은 실제 함정(같은 이름의 다른 터널로 DNS가 잘못 라우팅 → 터널 UUID + --overwrite-dns로 강제)까지 사례로 다룬다. HTTPS가 어디서 종료되는지.터널 config.yml + 도메인으로 접속되는 자기 앱(또는 임시 서브도메인) + DNS 연결 검증 캡처
5접근통제·안전 게이트를 켠 채 배포하기2h공개 즉시 아무나 개인정보를 흘릴 수 있다는 위험. 인증(세션·SESSION_SECRET)·가입 도메인 화이트리스트(ALLOWED_EMAIL_DOMAINS) 설정. 핵심: 입력단 PII 스캐너(lib/pii.ts)는 클라이언트 방어일 뿐, 서버 API 라우트에서 반드시 재검증(sandbox route의 scanPii→hasBlocking→차단)해야 하는 이유(클라이언트는 우회 가능). 배포 앱에 이 게이트가 실제로 살아있는지 테스트.인증·허용도메인 설정을 반영한 .env + '서버측 PII 재검증이 동작함'을 보이는 요청/차단 로그
6롤백·모니터링·배포 승인 — 되돌릴 수 있게, 지켜보며2.5hgit 태그로 버전 고정 → 문제 시 이전 커밋 체크아웃→재빌드→재시작(롤백 런북). 최소 모니터링: 헬스체크 URL, systemd 상태·로그, 오류율·비용(API 사용량) 관찰, systemd timer 헬스체크/워치독 소개. '공표 결재선' = 배포 승인 체크리스트(안전 게이트 통과·롤백 근거·소관자 지정)를 만든다. CI/CD는 이 수작업 절차의 자동화임을 개관.롤백 런북 1장 + 헬스체크 확인 결과 + '배포 승인 체크리스트'(팀 표준 초안)
7망분리 배포 대안 — 인터넷 없는 청사에 세우기2h행정망 현실: 외부 PaaS·외부 LLM API·공개 터널 불가. 대안 아키텍처 설계: 의존성 오프라인 반입(node_modules·모델 파일), 사내 서버 상주화(systemd 동일), 내부 도메인·사내 인증서, 리버스 프록시는 사내 Nginx, 외부 LLM 대신 온프레미스/로컬 LLM(예: Ollama). 무엇이 그대로 쓰이고(빌드·프로세스·접근통제·롤백) 무엇만 바뀌는지(통로·모델) 대조표 작성.'인터넷망 배포 vs 행정망 배포' 대조 아키텍처 표 + 부서 제안용 호스팅 선택 기준표

실습 랩

🧪랩 A — 정본 만들어 상주 서비스로 세우기(빌드→환경변수→systemd)

목표 · 개발모드로만 돌던 Next.js 앱을, 빌드한 정본으로 구동하고 systemd user 유닛으로 상주화(자동 재시작·부팅 자동시작)한다. 이 플랫폼(aiedu)의 실제 유닛을 교보재로 그대로 따라 만든다.

  1. 작업 폴더에서 `npm install` 후 `npm run build` 실행 → `.next` 산출물과 빌드 성공 로그를 확인한다(개발모드 next dev와의 차이를 눈으로 대조).
  2. 비밀 분리: `cp .env.example .env.local` 후 ANTHROPIC_API_KEY·SESSION_SECRET을 채운다. `chmod 600 .env.local` 로 권한을 제한하고, .gitignore에 포함돼 있는지 `git status`로 확인한다(키가 커밋 목록에 없어야 정상).
  3. 수동 구동 확인: `NODE_ENV=production npx next start -p 4500` 실행 후 브라우저에서 http://localhost:4500 접속. (주의: 3000 포트는 다른 프로젝트이므로 4500으로 테스트.)
  4. 상주화: `~/.config/systemd/user/` 에 `myapp-web.service`를 작성한다 — WorkingDirectory(앱 경로), EnvironmentFile(.env.local), ExecStart(node …/next/dist/bin/next start -p 4500), Restart=always, WantedBy=default.target. aiedu-web.service를 참고 템플릿으로 삼는다.
  5. `systemctl --user daemon-reload && systemctl --user enable --now myapp-web` 로 등록·기동. `systemctl --user status myapp-web` 로 active(running) 확인, `journalctl --user -u myapp-web -n 30` 로 로그 확인.
  6. 자동 재시작 실증: 프로세스를 강제 종료(kill)한 뒤 몇 초 후 다시 status를 보면 Restart=always로 되살아나 있어야 한다.

✅ 성공 기준 · 터미널을 닫아도 localhost:4500이 계속 응답하고, kill 후에도 자동 부활하며, `.env.local`이 git에 잡히지 않는다.

↗ 확장 · `loginctl enable-linger $USER` 로 로그아웃/재부팅 후에도 유닛이 자동 기동되게 만들고, 재부팅으로 실증한다.

🧪랩 B — 공개 통로 뚫고 안전 게이트 켠 채 배포하기(터널→접근통제→서버측 PII 재검증)

목표 · 랩 A의 로컬 서비스를 Cloudflare Tunnel로 도메인에 연결하고, 인증·허용도메인·서버측 PII 재검증이 살아있는 상태로 '안전하게' 공개한다.

  1. cloudflared 설치 후 `cloudflared tunnel login` → `cloudflared tunnel create myapp` 로 터널과 자격증명(UUID.json)을 만든다.
  2. `~/.cloudflared/myapp.yml` 작성: tunnel(UUID), credentials-file, ingress(hostname: 내 서브도메인 → service: http://localhost:4500, 마지막 http_status:404). aiedu.yml을 참고 템플릿으로 쓴다.
  3. DNS 연결: `cloudflared tunnel route dns <UUID> <내도메인> --overwrite-dns` — 반드시 이름이 아닌 UUID로 지정한다(이 플랫폼은 이름으로 하면 동명 다른 터널로 잘못 라우팅되는 함정을 겪었다).
  4. 터널도 상주화: `myapp-tunnel.service`(ExecStart=cloudflared … run <UUID>)를 만들어 `systemctl --user enable --now`. 외부에서 https://<내도메인> 접속을 확인한다.
  5. 접근통제 켜기: `.env.local`에 SESSION_SECRET(긴 랜덤)·ADMIN_*·ALLOWED_EMAIL_DOMAINS(예: 소속 도메인)를 설정하고 재시작. 허용 밖 도메인 가입이 막히는지 시험한다.
  6. 서버측 PII 재검증 실증: 이 플랫폼 sandbox API는 요청 본문을 lib/pii.ts의 scanPii로 재검사해 주민등록번호 등 block 항목이 있으면 외부 LLM 호출 전에 차단한다(app/api/sandbox/route.ts의 scanPii→hasBlocking→piiBlockMessage). 가짜 주민번호가 든 합성 입력을 보내 '서버가 차단'하는 응답/로그를 확인한다. 클라이언트 스캐너를 우회해도 서버가 막는다는 점을 검증한다.

✅ 성공 기준 · 외부 브라우저에서 https 도메인으로 앱이 열리고, 허용 밖 도메인은 가입이 막히며, 합성 주민번호 입력이 서버 단계에서 차단된다(외부 API로 전송되지 않음).

↗ 확장 · 헬스체크 경로를 주기 점검하는 systemd timer(또는 워치독)를 추가하고, git 태그로 현재 버전을 고정한 뒤 '이전 태그로 되돌리는' 롤백을 1회 실제로 수행해 런북에 기록한다.

사례 연구

📎 공공 사례 — aiedu 플랫폼 자신을 교보재로: systemd + Cloudflare Tunnel 무공인IP 배포

상황 · 경상북도·GDI 행정 실무자 교육 플랫폼(aiedu.gdiaxhub.com)을, 별도 공인 IP·인바운드 포트 개방 없이 상시 공개 서비스로 세워야 했다. 담당자 부재·재부팅에도 죽지 않아야 하고, AI 해설·인증 같은 서버 기능이 필요했다.

정적 export 대신 서버 구동(next start -p 4500)을 택했다(인증·API 라우트·AI 해설 때문). 웹은 systemd user 유닛(aiedu-web.service, Restart=always, EnvironmentFile=.env.local)으로 상주화해 자동 재시작·부팅 자동시작을 보장했다. 공개 통로는 인바운드 포트를 여는 대신 Cloudflare Tunnel(aiedu-tunnel.service가 cloudflared를 UUID로 run)로 뚫어, 서버가 바깥으로 연결을 걸어 도메인(CNAME→터널)으로 접속되게 했다. 비밀(ANTHROPIC_API_KEY·SESSION_SECRET)은 .env.local(chmod 600·git 제외)에 분리했다. 실제 함정: `tunnel route dns`를 터널 '이름'으로 실행하자 동명의 다른 터널로 DNS가 잘못 라우팅됐고, 터널 UUID + --overwrite-dns로 강제해 해결했다. 로컬 테스트는 4500으로만 했다(3000은 다른 프로젝트라 로그인 요구 응답을 내어 혼동을 유발).

교훈 · 공인 IP·포트 개방 없이도 터널로 안전 공개가 가능하며, '자동 재시작·부팅 자동시작·비밀 분리'가 배포의 최소 3종 세트다. DNS 라우팅은 사람이 읽는 이름이 아니라 고유 식별자(UUID)로 지정해야 오배선을 막는다.

📎 일반 사례 — API 키를 프론트엔드에 심어 배포했다가 유출된 스타트업

상황 · 한 팀이 서버 없이 빠르게 내려고 정적 사이트로 배포하면서, 유료 LLM API 키를 클라이언트 자바스크립트에 직접 넣었다. 배포는 하루 만에 끝났고 잘 작동하는 듯 보였다.

정적 배포는 서버가 없으므로 키를 숨길 곳이 없다 — 브라우저에 내려간 코드는 누구나 개발자도구로 열람 가능하다. 며칠 뒤 키가 크롤러에 수집돼 무단 호출로 요금이 폭증했다. 근본 원인은 두 가지: (1) 비밀을 코드에 박음(환경변수 미분리), (2) 비밀이 필요한 기능을 정적 배포로 냄(서버 없는 곳에 서버의 몫을 맡김). 올바른 형태는 키를 서버 환경변수에 두고, 클라이언트는 자기 서버의 얇은 API 라우트만 부르며, 그 라우트가 서버에서 외부 API를 대신 호출(+입력 재검증)하는 구조다.

교훈 · '클라이언트에 내려간 것은 비밀이 아니다.' 비밀이 필요한 기능은 서버 렌더링/서버 라우트가 필수이며, 정적 vs 서버 선택은 편의가 아니라 보안 요구가 결정한다. 안전 검증(PII·인증)은 반드시 서버에서 재수행해야 한다.

흔한 실패 · 안티패턴

  • 개발모드(next dev)를 그대로 대민 서비스로 켠다 — 느리고 불안정하며 소스가 노출될 수 있다. 반드시 build→start 정본으로.
  • 터미널 세션에서 앱을 띄워두고 창을 닫아 서비스가 죽는다. 프로세스 매니저(systemd/pm2)로 상주화하지 않으면 배포가 아니다.
  • API 키·비밀번호를 코드나 git에 커밋한다. 한 번 올라간 비밀은 히스토리에 영구 잔존 — 즉시 회전(재발급)해야 한다. .env는 반드시 .gitignore.
  • 비밀이 필요한 기능을 정적 export로 배포해 키가 클라이언트에 노출된다. 인증·DB·외부 API 호출이 있으면 서버 구동이 필수.
  • 클라이언트 PII 스캐너만 믿고 서버 재검증을 생략한다. 클라이언트 검증은 우회 가능 — 서버 API 라우트에서 반드시 재검사해야 실효.
  • DNS를 터널/서비스 '이름'으로만 연결해 동명의 다른 대상으로 오배선된다. 고유 식별자(UUID)로 지정하고 연결을 검증하라.
  • 롤백 경로를 준비하지 않고 배포한다. git 태그·버전 고정이 없으면 사고가 고착된다 — '되돌릴 수 있게' 배포하는 것이 설계의 일부.
  • 모니터링 없이 '떴으니 끝'이라 여긴다. 헬스체크·로그·비용을 안 보면 장애·요금 폭증을 사용자가 먼저 발견한다.
  • 망분리(행정망)를 고려 않고 '클라우드에 올리면 되지'로 설계한다. 외부 API·공개 터널 불가 환경에선 온프레미스·로컬 LLM 대안이 처음부터 필요.
  • 로컬 테스트 포트를 혼동한다(이 플랫폼은 4500이 정답, 3000은 다른 프로젝트). 무엇을 테스트하는지 포트·프로세스를 확인하라.

평가

평가 기준초급능숙전문
빌드·상주화(정본 구동과 프로세스 관리)next dev로만 띄울 수 있고, 창을 닫으면 서비스가 죽는다. 빌드와 개발모드의 차이를 설명하지 못한다.build→start로 정본을 구동하고 systemd/pm2로 상주화해 자동 재시작을 건다. 로그(journalctl)로 상태를 확인한다.부팅 자동시작(linger)·재시작 정책·로그 회전까지 고려한 유닛을 설계하고, 재부팅·크래시 시나리오를 실증하며 타인에게 재현 가능한 문서를 남긴다.
비밀·환경변수 관리키를 코드에 박거나 .env를 커밋한다. 유출 위험을 인지하지 못한다.비밀을 .env로 분리하고 git에서 제외하며 파일 권한을 제한한다. 필요한 값만 실행 시 주입한다.환경별 분리·키 회전 절차·유출 시 대응(즉시 재발급)을 규율화하고, '비밀은 클라이언트에 내려가지 않는다'는 원칙을 아키텍처로 강제한다.
공개 통로(프록시·도메인·터널)도메인이 어떻게 내부 포트로 이어지는지 설명하지 못하고, 인바운드 포트를 무분별하게 연다.터널 또는 리버스 프록시로 도메인을 내부 앱에 연결하고 DNS를 검증한다. HTTPS 종료 지점을 안다.공인 IP 없이 터널로 안전 공개하고, DNS 오배선(동명 대상) 같은 함정을 UUID 지정으로 예방하며 통로 선택의 트레이드오프를 설명한다.
접근통제·안전 게이트공개하면 아무나 접근·개인정보 유출이 가능함을 인지하지 못한다.인증·허용 도메인·세션을 설정하고, 서버 API에서 PII를 재검증하도록 켠다.클라이언트 검증의 우회 가능성을 전제로 서버측 재검증을 필수화하고, 합성 데이터로 차단을 실증하며 안전 게이트 통과를 배포 승인의 필수 항목으로 만든다.
롤백·모니터링·운영 판단(망분리 포함)되돌릴 방법이 없고 모니터링을 하지 않는다. 망분리 환경을 고려하지 못한다.git 태그로 롤백 절차를 갖추고 헬스체크·로그·비용을 관찰한다. 호스팅 선택 기준을 안다.롤백 런북·배포 승인 체크리스트를 표준화하고, 인터넷 차단 행정망에서 외부 API·공개 터널 없이 동일 서비스를 세우는 대안 아키텍처를 설계·제안한다.
사전 점검
  • '내 노트북에서 잘 되는데 배포는 왜 따로 필요한가?'를 한 문장으로 답해 보세요.
  • AI 도구에 쓰는 API 키를 어디에 두어야 하고, 왜 코드에 넣으면 안 되는지 아는 대로 적어 보세요.
  • 여러분이 지금 쓰는 aiedu.gdiaxhub.com은 어떤 컴퓨터에서, 어떻게 항상 켜져 있다고 생각하나요? (모르면 '모른다'와 그 이유)
  • 서비스를 새로 배포했더니 오히려 고장 났습니다. 무엇을 미리 준비했어야 되돌릴 수 있을까요?
사후 점검
  • 개발모드 실행과 배포용 정본(build→start) 구동의 차이를, '왜 대민 서비스에 개발모드를 쓰면 안 되는가'를 포함해 설명하세요.
  • 공개 주소(도메인)에서 사용자의 요청이 실제 앱(localhost:4500)에 닿기까지의 경로를, 터널·프로세스 매니저·환경변수 주입을 포함해 서술하세요.
  • 정적 export와 서버 구동 중 무엇을 택할지, '인증·DB·외부 AI 호출이 있는 앱'을 예로 근거와 함께 판단하세요. 정적으로 배포하면 무엇이 위험해지나요?
  • 클라이언트에 PII 스캐너가 있는데도 서버 API에서 다시 검사해야 하는 이유를 설명하고, 이를 배포 승인 체크리스트에 어떻게 반영할지 쓰세요.
  • 인터넷이 차단된 행정망에 이 서비스를 세운다면, 무엇을 그대로 쓰고(빌드·프로세스·접근통제·롤백) 무엇을 바꿔야 하는지(공개 통로·외부 LLM) 대조해 설계하세요.

읽을거리 · 도구

  • Next.js 공식 문서 — Deploying / Self-Hosting — 이 플랫폼과 같은 Next.js 앱의 배포 옵션(관리형 vs 자체호스팅)·`next build`/`next start`·정적 export·환경변수 처리를 1차 자료로 확인 (버전이 자주 바뀌므로 수치·명령을 외우지 말고 '현재 버전 문서에서 self-host 절차와 환경변수 규칙을 어떻게 안내하는가'를 확인하는 용도로 읽어라. 이 플랫폼은 서버 구동을 택했음을 대조.)
  • Cloudflare Tunnel(cloudflared) 공식 문서 — 터널 생성·config·DNS 라우팅 — 이 플랫폼이 실제로 쓰는 방식. 공인 IP·포트 개방 없이 로컬 서비스를 도메인에 잇는 원리와 ingress·credentials·route dns 명령을 확인 (명령 플래그는 변할 수 있으니 '왜 인바운드 포트를 안 열어도 되는가(아웃바운드 연결 원리)'와 DNS를 식별자로 지정하는 이유에 초점을 둬라.)
  • systemd 매뉴얼 — user 서비스와 유닛 파일(systemd.service, systemctl --user, journalctl) — 서비스를 상주화하는 표준 방법. WorkingDirectory·EnvironmentFile·ExecStart·Restart·WantedBy의 의미와, enable/start/status/log 확인법 (이 플랫폼의 aiedu-web.service를 옆에 두고 각 지시어가 무엇을 보장하는지 대조하며 읽어라. pm2와 언제 무엇을 쓸지도 스스로 정리.)
  • OWASP — Secrets Management 관련 가이드(Cheat Sheet) — 비밀(키·비밀번호)을 코드/깃에 넣지 않기, 회전, 최소권한 등 배포에서 가장 흔한 사고를 막는 원칙 (특정 도구 광고가 아니라 '원칙'을 취하라. 이 플랫폼의 .env.local(chmod 600·git 제외) 관행이 어느 원칙의 최소 구현인지 매핑해 보라.)
  • Ollama 공식 문서 — 로컬 LLM 실행 — 망분리(행정망)에서 외부 LLM API 대신 온프레미스로 모델을 돌리는 실사용 경로. 서비스 골격(빌드·프로세스·접근통제)은 그대로 두고 '모델 통로'만 사내로 바꾸는 대안 (모델 성능·요구 사양은 환경에 크게 좌우되니 '무엇이 오프라인에서 가능/불가능한가'의 경계를 파악하는 데 써라.)

다른 계층과의 연결

실습 환경 준비선행. 배포는 env-setup에서 갖춘 로컬 실행 환경(Node·의존성·`.env`·git)을 전제로, 그 실행물을 '상시 서비스'로 승격시키는 다음 단계다. 오프라인 반입·권한 같은 환경 규율이 망분리 배포에서 그대로 재등장한다.
하네스 엔지니어링운영 장치. 하네스가 만든 프로세스 매니저·환경변수·비밀·접근통제·재시작 정책이 배포의 실행 골격 그 자체다. 배포는 하네스 개념을 실제 서버에 적용해 상주화하는 장이다.
오케스트레이션·에이전트 엔지니어링다구성요소. 여러 서비스(웹·터널·워커·헬스체크)를 함께 세우고 순서·의존(After=)·통로(리버스 프록시)로 엮는 것은 오케스트레이션의 배포판이다. CI/CD 파이프라인도 이 결의 자동화다.
평가 엔지니어링품질 게이트. 배포 전 '통과해야 넘어오는' 검사(빌드·타입체크·안전 게이트·헬스체크)와 롤백 판단 기준을 eval-eng가 제공한다. 배포 승인 체크리스트는 평가 결과를 결재 조건으로 삼는다.

🤖 AI 해설

AI 해설은 이 항목의 근거에 기반한 보조 자료입니다. 중요한 수치·사실은 원문 출처로 검증하세요.