본문으로 건너뛰기

운영자 가이드 — 서버 운영, 배포, 장애 대응을 스킬과 함께

대상: HandStack 서버(ack/rdy 호스트)를 운영하거나 배포를 맡은 사람(인프라·운영 엔지니어, 운영을 겸하는 개발자).

목차

  1. 첫 주: 내 환경 파악하기
  2. 일상 운영 점검
  3. 설정 바꾸기 (반영 방식)
  4. 배포: 방식 선택 → 파이프라인 → 승인 → 롤백
  5. 장애 대응
  6. 위험 명령 목록
  7. 보안 점검
  8. 신호를 의도로 되돌리기 (유지보수 순환)

1. 첫 주: 내 환경 파악하기​

운영 사고의 대부분은 (1) 설정이 어느 파일에서 오는지 모르거나, (2) 빌드·배포 명령이 실행 폴더를 덮어쓰거나, (3) 재시작이 필요한 설정을 실시간 반영으로 오해해서 생깁니다. 먼저 아래 표를 채우세요(비밀 값은 적지 않습니다).

항목확인 방법내 환경
호스트 종류와 포트ack 8421, rdy 8420, forbes 8420(같은 호스트에 함께 두면 충돌), agent 8422, deploy 8520 — 운영 호스트
HANDSTACK_HOME환경 변수 확인, env.* 스크립트
환경 구분ACK_ENVIRONMENT(D/S/P)
로드되는 모듈appsettings.json의 LoadModules, --modules 인자
설정 파일 계층appsettings.json → OS별 → 환경 변수 AppSettings__키 → appsettings.{환경}.json(뒤가 앞을 덮어씀) — 설정을 읽는 순서
배포 방식폴더 반영 / 업데이트 패키지 / Docker
프로세스 관리서비스, IIS 앱 풀, pm2, 컨테이너
로그 위치${HANDSTACK_HOME}/log/app*.log (컨테이너는 docker logs)
백업/롤백 방법

AI에게 시킬 때:

/handstack-host-operations 이 서버의 구성을 파악하려고 해. 읽기 전용으로만 진행해 줘.
HANDSTACK_HOME 아래의 폴더 구성, 로드되는 모듈 목록, 설정 파일 계층(어떤 파일이 어떤 순서로 덮어쓰는지),
재시작이 필요한 설정과 필요 없는 설정을 표로 정리해 줘. 비밀 값은 열지 말고 키 이름만 알려 줘.

기대 결과: 위 표와 같은 정리. 확인할 것: AI가 module.json/appsettings의 비밀 값을 출력하지 않았는지.

2. 일상 운영 점검​

curl -I http://localhost:8421 # 200
curl http://localhost:8421/checkip
curl "http://localhost:8421/diagnostics?hostAccessID=<SHA-256 해시>"
  • 관리 API의 hostAccessID에는 AppSettings:HostAccessID 평문이 아니라 SHA-256 해시를 보냅니다(불일치가 401의 흔한 원인). 엔드포인트 목록은 관리 엔드포인트를 보세요.
  • 모듈 관리 API는 AuthorizationKey/AllowClientIP로 판정하고 실패하면 401이 아니라 400입니다(계약 관리 API 공통 규칙). 인증을 우회해 호출하지 않습니다.
  • 매일 볼 것: 프로세스/컨테이너 상태, 로그의 오류 추이, 디스크(log, sqlite, storage), 인증서 만료, 대표 거래 1건 응답.

3. 설정 바꾸기 (반영 방식)​

변경 전에 "이 설정은 재시작이 필요한가?"를 먼저 답합니다.

실시간 반영되는 키와 재시작이 필요한 키의 전체 목록은 실시간 반영과 재시작이 필요한 설정에 있습니다. IsConfigurationWatching=true일 때 HostAccessID, BusinessServerUrl, IsExceptionDetailText, WithOnlyIPs 같은 일부 키와 로드된 모듈의 module.json(데이터 원본 포함)이 재시작 없이 반영되고, LoadModules, CORS, 프록시, 세션, DLL 추가·교체, 새 모듈, OS별 파일, 환경 변수는 재시작이 필요합니다.

/handstack-host-operations 로그 상세 메시지(IsExceptionDetailText)를 개발 서버에서만 켜고 싶어.
어떤 파일의 어떤 키를 바꾸고, 재시작이 필요한지, 반영됐는지 확인하는 방법과 되돌리는 방법을 알려 줘.
운영 서버 설정은 바꾸지 말고 계획만 줘.

원칙: 운영 설정 변경도 코드처럼 PR(또는 변경 요청)로 남기고, 변경 후 확인 방법과 되돌리는 값을 같이 기록합니다.

4. 배포: 방식 선택 → 파이프라인 → 승인 → 롤백​

4-1. 방식은 환경마다 하나​

방식적합핵심 주의
폴더 반영(복사/미러링 + 재시작)단일 서버, 모듈·계약·정적 파일 위주 변경공유 contracts 미러링 금지, 서비스 정지 후 복사, 설정 파일 환경 일치
업데이트 패키지(publish → publish-package → deploy → updater)여러 서버, 버전 단위 증분 배포contracts는 표준 패키지에 없음, 업로드 키 필수, 적용 중 오류 시 자동 복원 없음
Docker(ack 이미지)컨테이너 운영볼륨이 비어 있을 때만 시드 복사 → 이미지 교체만으로 앱이 갱신되지 않음

4-2. 현재 상태 점검부터​

$SK = "C:\path\to\project\.claude\skills"
python "$SK\handstack-cicd-pipeline\scripts\pipeline_lint.py" . --min-severity WARN

찾는 것: 비밀 리터럴(값은 마스킹됨), 공유 contracts 미러링, HANDSTACK_HOME 삭제, 인자 없는 handstack stop, publish의 홈 격리 누락, 운영 잡의 개발 설정 복사, Docker 기반 이미지 버전·고정, 배포 호스트 ManagementKey 공백.

/handstack-cicd-pipeline 현재 저장소의 빌드·배포 스크립트와 CI 정의를 읽고 pipeline_lint 결과를 정리해 줘.
(1) 지금 파이프라인의 단계를 표로, (2) 안전하지 않은 지점과 고치는 순서,
(3) 격리 스모크와 롤백이 빠져 있다면 추가하는 방법을 제안해 줘. 서버에는 아무것도 반영하지 마.

4-3. 파이프라인 8단계 (요약)​

소스 고정 → 정적 점검 → 빌드 → 패키징 → 격리 스모크(임시 HANDSTACK_HOME에서 ack 기동, /checkip, 대표 거래) → 릴리즈 기록 → 배포(운영은 승인) → 배포 후 확인. 단계별 명령과 CI 골격은 handstack-cicd-pipeline의 references/pipeline-stages.md.

핵심 규칙:

  • 러너의 HANDSTACK_HOME은 운영 홈과 다른 경로입니다. publish.*는 대상 폴더와 HANDSTACK_HOME/contracts를 삭제하고, 모듈 빌드 후처리가 홈을 덮어쓰는 프로젝트가 많습니다.
  • 운영 잡은 수동 또는 승인 단계가 있습니다. 승인자와 승인 시각을 릴리즈 기록에 남깁니다.
  • 운영 서버 파일을 직접 고치지 않습니다. 불가피했다면 기록하고 소스에 되돌립니다.

4-4. 승인 관문을 도구로 강제하기​

AI(에이전트)가 파이프라인을 돕더라도 운영 반영 직전까지만 하게 합니다. deploy_gate.py 훅은 위험 명령(publish.*, purgecontracts, handstack stop, 삭제 동기화, 업데이트 업로드, 컨테이너 삭제)을 RELEASE_APPROVAL 환경 변수가 설정되지 않으면 차단합니다.

(승인 없이) 에이전트: ./publish.ps1 win publish Release x64
→ 운영 관문: publish 스크립트는 대상 폴더와 HANDSTACK_HOME/contracts를 삭제한다. 릴리즈 승인(RELEASE_APPROVAL) 없이는 실행할 수 없습니다. …

릴리즈 관리자가 승인 번호를 세션 밖에서 지정해야 의미가 있습니다. 에이전트가 스스로 변수를 설정하지 못하게 하려면 관리형 설정에서 훅을 비활성화할 수 없게 두세요. 설치 방법은 개발자 가이드 5절.

4-5. 릴리즈 기록​

매 배포마다 릴리즈 기록(빌드 시각, 브랜치·커밋, 매개변수, 모듈 목록, 대상 환경, 적용 설정 파일 이름, 패키지/이미지 식별자, 배포 후 확인, 승인자)을 남깁니다. 같은 명령으로 산출물을 다시 만들 수 있어야 합니다.

4-6. 배포 후 확인과 롤백​

배포 후 확인(순서대로, 하나라도 실패하면 롤백 판단):

  1. 프로세스/컨테이너 기동, 최근 로그 오류 없음
  2. /checkip
  3. 버전(GET /manifest, app/version.json, log/update/updater.log)
  4. 대표 조회 거래 + 이번 변경 화면/거래 (변경 거래는 검증 환경에서)
  5. 권한 없는 계정이 거부되는지

롤백은 배포 전에 계획합니다.

방식되돌릴 대상
폴더 반영반영 전에 만든 app·modules/{모듈}·contracts·설정 사본
업데이트 패키지updater가 만든 backup/<시각>/<버전>/…. 자동 복원은 없음 — 서비스 정지 후 수동 복원
Docker이전 이미지 + 이전 볼륨 상태

DB 변경(DDL/DML)이 포함되면 코드 롤백만으로 복구되지 않습니다. 되돌리는 스크립트나 복구 시점을 같이 준비하고, 복원은 비운영 환경에서 연습하세요.

5. 장애 대응​

5-1. 처음 5분​

  1. 증상·시각·환경·최초 오류 문구·재현 절차를 적습니다(비식별).
  2. 로그와 응답 본문의 exceptionText, GlobalID를 확보합니다(토큰·쿠키는 제거).
  3. 한 번에 하나만 바꿉니다. 설정을 풀거나 데이터를 지워서 증상을 없애는 것은 해결이 아닙니다.

5-2. AI에게 맡기기​

/handstack-troubleshooting 사용자가 "거래 응답 정의와 데이터 갯수가 다릅니다"를 보고했다.
화면 ID는 SUP010, 조회 버튼, 오늘 14:20부터. 어느 층에서 처음 틀어졌는지 판정하는 순서와
각 층에서 확인할 파일/로그를 알려 줘. 서버 설정은 바꾸지 말고 읽기만 해.

기대 결과: 진단 사다리(접속 → 화면 → 바인딩 → 전송·인증·권한 → 계약 매핑 → 실행 → 응답)에서 후보 층과 확인 방법. 확인할 것: 원인 후보마다 근거(파일/로그)를 대는지.

5-3. 자주 만나는 메시지​

메시지/증상먼저 볼 것
401, BearerToken …, 거래 액세스 토큰 …로그인 상태·토큰 만료·IP, 외부 호출자는 X-Api-Key
인증 자격 증명 확인 필요토큰 만료가 아니라 PermissionRoles 정규식이 요청 경로와 맞지 않음
요청 가능화면 거래 매핑 정보 확인 필요.txn의 AccessScreenID에 호출 화면이 없음
요청 만료, 중복 요청, 잘못된 요청서버의 요청 검증(IsValidationRequest/IsValidationGlobalID) — 1회용 GlobalID
요청 한도 초과SecurityHardening.RateLimit가 Enforce
제한된 거래 요청입니다.테넌트 앱 경로 호출의 CommandType이 AllowTenantTransactionCommands에 없음
관리 API 401 / 400hostAccessID 해시 불일치(401) / AuthorizationKey·AllowClientIP 불일치(400)
수정했는데 예전 동작수정했는데 예전 동작이 나옴
모듈이 안 올라옴LoadModules 이름 = module.json의 ModuleID = DLL 이름, 새 모듈은 재시작

모든 문구의 발생 조건과 확인할 설정은 거래 오류 메시지에 있고, 진단 순서는 handstack-troubleshooting 스킬이 안내합니다.

5-4. 사후​

  • 원인, 첫 실패 층과 근거, 수정, 재시험 결과(정상·빈 결과·오류), 재발 방지를 기록합니다.
  • 수정이 배포되면 같은 유형을 막는 점검을 추가합니다(점검 규칙, AGENTS.md 한 줄, 테스트 케이스). 장애 기록이 새 intent.md가 되는 흐름은 8절.

6. 위험 명령 목록​

실행 전에 대상 경로를 출력해서 확인하고, 승인 없이는 에이전트가 실행하지 못하게 훅으로 막으세요.

명령무슨 일이 생기나
publish.*대상 publish 폴더 삭제, HANDSTACK_HOME/contracts 삭제
모듈/호스트 buildPostBuild가 `HANDSTACK_HOME/app
handstack purgecontracts계약 파일 삭제
handstack stop (인자 없음)같은 호스트의 모든 ack와 dotnet 프로세스 종료 → 반드시 --pid나 --port 지정
robocopy /mir, rsync --delete대상 폴더의 다른 파일 삭제 — 공유 contracts에는 쓰지 않음
assemblies.*공통 DLL 교체
Docker 볼륨 마운트 변경, docker compose down -v데이터 유실 가능

7. 보안 점검​

월 1회 또는 배포 전에 확인하세요.

  • pipeline_lint.py에 secret 0건. 발견되면 값을 폐기·재발급(저장소 이력 정리 여부도 판단)
  • 배포 호스트(deploy)의 ManagementKey가 비어 있지 않다(비어 있으면 업로드 API가 인증 없이 열림). 업로드 경로는 내부망/승인된 러너로 제한
  • 모듈의 AllowClientIP 기본값 *(개발용)을 운영에서 좁혔다
  • IsEnabledDevAutoSignIn은 개발(D) 환경에만
  • 관리 API 키·HostAccessID는 환경 변수/비밀 저장소에서 주입하고 로그·문서에 평문이 없다
  • 업데이트 매니페스트·패키지 경로는 HTTPS와 접근 제어(updater는 크기·SHA-256만 검증, 서명 검증 로직 없음)
  • 운영 서버에 에이전트용 쓰기 자격 증명을 두지 않았다

8. 신호를 의도로 되돌리기 (유지보수 순환)​

장애가 끝나면 "고치고 끝"이 아니라 다음 개선의 입력으로 되돌립니다. 처음에는 사람이 손으로 하고, 익숙해지면 일부를 자동화하세요.

  1. 신호 정하기: 대표 거래 실패율, 배포 후 오류 건수, 특정 오류 메시지 빈도 등 기준선이 안정적인 지표 하나.
  2. 탐지는 결정론적 스크립트로(기준선 대비 이탈 여부). AI는 이탈한 뒤에 호출해 진단만 하게 합니다.
  3. 대응 수준을 문서로 고정합니다.
수준대응AI 권한
경미로그만 기록없음
주의읽기 전용 진단, 후보 원인 정리읽기
위험수정안 PR 또는 롤백 제안제안만 — 실행은 사람 승인
  1. 진단 결과를 intent.md 형식(현상과 증거, 원하는 결과, 영향 시스템, 미해결 질문)으로 기록해 개발 흐름에 넣습니다.
  2. 수정 후에는 같은 유형을 막는 테스트·점검을 추가합니다.
/handstack-troubleshooting 어제 22:10~22:40 대표 조회 거래 실패율이 평소의 3배였다. 첨부한 로그 발췌와
릴리즈 기록을 보고 (1) 가능한 원인 후보를 근거 순으로, (2) 확인에 필요한 추가 자료,
(3) 개발 흐름에 넣을 intent.md 초안을 작성해 줘. 서버 설정은 바꾸지 말고 제안만 해.