ai-admin 관리자 가이드

v1.2.19 기준입니다. 이 문서는 ai-admin을 설치하고 지키는 사람을 위한 것입니다. 화면을 쓰는 방법은 사용자 가이드에 있습니다.

화면 캡처는 합성 데이터로 채운 실제 ai-admin 화면을 1440×1024에서 찍은 것입니다. 캡처 도구가 가린 [민감정보 마스킹], [표 데이터 마스킹], [API 키 마스킹], [내부 URL 마스킹] 자리에는 실제 화면에서 해당 값이 그대로 표시됩니다.

깊이 들어가는 내용은 다음 문서에 있고 여기서는 링크만 겁니다.


1. 구성 요소

ai-admin은 Go 서버 하나가 React 정적 파일을 내장한 단일 컨테이너입니다. 서버 자체는 상태를 갖지 않고 모든 상태는 PostgreSQL에 있습니다.

구성 요소 필수 역할
ai-admin 컨테이너 HTTP :8080 하나로 UI, REST API, OpenAI 호환 AI 프록시, MCP를 제공
PostgreSQL ai_admin 스키마(신규 관리 데이터)와 레거시 AI Portal 스키마를 모두 보관
Keycloak (OIDC) 아니요 SSO 로그인. 끄면 로컬 계정만 사용
사내 AI 엔드포인트 아니요 관리자가 등록하는 OpenAI 호환 공급자. 없으면 AI 기능만 쓰지 못함
레거시 Java AI Portal 아니요 같은 DB를 공유하는 기존 서비스. 함께 운영할 때만 해당

주고받는 것

방향 대상 내용
브라우저 → ai-admin :8080 세션 쿠키 + CSRF 쿠키로 인증한 REST 호출
자동화 → ai-admin :8080 Authorization: Bearer aia_... API 키. /v1/chat/completions, /v1/models, /mcp
ai-admin → PostgreSQL 5432 최대 20 · 최소 2 연결 풀
ai-admin → Keycloak 443 등 discovery, 토큰 교환, JWKS
ai-admin → AI 공급자 공급자 Base URL OpenAI 호환 요청 중계(스트리밍 포함)

Compose 파일은 PostgreSQL을 함께 띄우지 않습니다. POSTGRES_DSN의 호스트는 컨테이너에서 실제로 닿는 주소여야 합니다.


2. 설치

릴리즈 자산으로 처음부터 끝까지 진행합니다. 인터넷이 연결된 빌드 환경에서 직접 빌드하는 방법은 README에 있습니다.

2.1 필요한 자원

항목
노출 포트 8080/tcp 하나
볼륨 없음. 컨테이너는 read_only로 뜨고 /tmp만 64MB tmpfs
컨테이너 권한 cap_drop: ALL, no-new-privileges:true
필요한 바깥 연결 PostgreSQL, (선택) Keycloak, (선택) AI 공급자 엔드포인트
DB 권한 ai_admin 스키마 생성·변경(DDL)과 레거시 스키마 읽기, 관리 대상 테이블 쓰기

2.2 이미지 반입

GitHub Release에서 ai-admin-v1.2.19.tar.gzSHA256SUMS를 받아 승인된 매체로 반입합니다. 반입 전후 모두 체크섬을 확인하세요. 공식 아카이브는 Linux amd64입니다.

sha256sum -c SHA256SUMS
gzip -t ai-admin-v1.2.19.tar.gz
gzip -dc ai-admin-v1.2.19.tar.gz | docker load
docker image inspect ai-admin:v1.2.19 --format ''

2.3 환경 파일과 기동

compose.offline.yml.env를 같은 디렉터리에 둡니다. .env에는 아래 네 개만 넣습니다.

openssl rand -base64 32   # ENCRYPTION_KEY 로 쓸 값을 만든다
POSTGRES_DSN=postgres://ai_admin:CHANGE_ME@postgres.example.com:5432/aiportal?sslmode=require
BOOTSTRAP_ADMIN=admin@example.com
BOOTSTRAP_ADMIN_PASSWORD=CHANGE_ME_AT_LEAST_12_CHARS
ENCRYPTION_KEY=REPLACE_WITH_BASE64_32_BYTES
docker compose --env-file .env -f compose.offline.yml up -d
docker compose -f compose.offline.yml ps
curl --fail http://localhost:8080/health/live
curl --fail http://localhost:8080/health/ready
curl --fail http://localhost:8080/api/v1/meta

Compose를 쓰지 않으면 같은 설정을 docker run으로 줍니다.

docker run -d --name ai-admin --restart unless-stopped \
  --env-file .env -p 8080:8080 \
  --read-only --tmpfs /tmp:size=64m,noexec,nosuid \
  --cap-drop ALL --security-opt no-new-privileges \
  ai-admin:v1.2.19

시작할 때 DB 연결 → migration → seed 순으로 진행하며, 하나라도 실패하면 프로세스는 종료됩니다.

2.4 최초 관리자 계정

BOOTSTRAP_ADMIN·BOOTSTRAP_ADMIN_PASSWORD로 만든 계정이 최초 로컬 서비스 관리자입니다.

브라우저에서 http://<호스트>:8080을 열고 이 계정으로 로그인합니다.

로그인 화면 — 아이디·비밀번호와 카드 아래 서비스 이름·버전

로그인 카드 아래의 버전이 배포한 이미지 태그와 같은지 확인하세요.

2.5 최초 로그인 후 점검

대시보드 — 활성 사용자·AI 요청·토큰 사용량·승인 대기와 AI 공급자 상태

  1. 대시보드가 뜨고 지표가 오류 없이 계산되는지 확인합니다.
  2. 프로필 메뉴에서 서비스 버전을 다시 확인합니다.
  3. 시스템 설정 → 서비스 기본에서 서비스 표시 이름과 서비스 Public URL을 확인합니다.
  4. 레거시 스키마에서 실제 스키마 이름과 테이블 목록이 보이는지 확인합니다.
  5. 두 번째 서비스 관리자 또는 복구 절차를 확보한 뒤에 SSO를 켭니다.

3. 설정

3.1 환경 변수 (전수)

ai-admin이 읽는 환경 변수는 다음 네 개가 전부입니다. 나머지 운영 설정은 모두 관리자 화면에 있습니다. 예시 값은 모두 가짜입니다.

이름 기본값 필수 설명
POSTGRES_DSN 없음 레거시 스키마와 ai_admin 스키마가 있는 PostgreSQL DSN. 컨테이너에서 접속 가능해야 함. 예: postgres://ai_admin:CHANGE_ME@postgres.example.com:5432/aiportal?sslmode=require
BOOTSTRAP_ADMIN 없음 최초 로컬 서비스 관리자 아이디. 예: admin@example.com
BOOTSTRAP_ADMIN_PASSWORD 없음 위 계정의 비밀번호. 12자 이상이어야 하며 미만이면 시작을 거부
ENCRYPTION_KEY 없음 저장 비밀값용 AES-256 키. 정확히 32바이트를 표준(또는 패딩 없는) Base64로 인코딩. openssl rand -base64 32

수신 주소는 코드에 :8080으로 고정되어 있어 환경 변수로 바꿀 수 없습니다. 다른 포트로 노출하려면 컨테이너 포트 매핑(-p 9090:8080)을 바꾸세요.

네 값 중 하나라도 비어 있으면 로그에 configuration rejected와 함께 빠진 이름이 나오고 프로세스가 종료됩니다.

3.2 화면에서 관리하는 설정

시스템 설정에서 나머지를 전부 다룹니다. 비밀 설정은 암호화 저장되며 다시 표시되지 않습니다. 비밀 입력란을 빈 칸으로 두고 저장하면 기존 값이 유지됩니다.

시스템 설정 — 서비스 기본 탭과 왼쪽 설정 그룹 목록

그룹 다루는 것
서비스 기본 서비스 표시 이름, 서비스 Public URL(OIDC 콜백 계산 기준)
Keycloak SSO issuer, client id/secret, scope, 관리자 역할 매핑, HTTP issuer 허용, 저장된 설정 진단
보안·세션 세션 유효 시간(분), HTTPS 전용 쿠키
AI 기본값 기본 공급자, 기본 스트리밍 여부
승인 전체 설정 검토·승인 전역 스위치
화면 기본 테마, 컴팩트 모드
레거시 연결 레거시 스키마 이름
레거시 API / 배치 / 인증 / 저장소 기존 Java YAML 운영 값. 재시작 배지가 붙습니다

레거시 API·배치·인증·저장소 네 그룹은 저장한다고 실행 중인 Java 프로세스에 반영되지 않습니다. 왼쪽 탭에 재시작 배지가 붙고, 화면 제목 옆에 레거시 Java 재시작 필요 표시와 함께 배포 절차 안내가 나옵니다.

레거시 연동 API 설정 — 재시작 필요 표시와 배포 자동화 안내

반영 절차는 YAML export → 설정 파일로 배치 → 레거시 Java 재시작입니다. ai-admin은 Java 적용 완료 여부를 판별하거나 표시하지 않으므로 배포 기록과 smoke test로 추적하세요. legacy_runtime_config 승인이 켜져 있으면 저장 요청 자체가 먼저 대기 상태가 되고, 최종 승인 후에도 export·배포·재시작은 따로 해야 합니다.

배포 자동화는 브라우저가 아니라 전용 API 키로 다음을 호출합니다(보안상 브라우저 세션에서는 내려받을 수 없습니다). 역할에 settings.export, 키에 legacy:config scope가 모두 필요합니다.

GET /api/v1/settings/legacy/application-managed.yaml

내려받은 파일에는 복호화된 비밀값이 들어갈 수 있습니다. 배포 secret으로 취급하고 적용 후 파기하세요. 자세한 내용은 레거시 관리 설계에 있습니다.

3.3 Keycloak SSO

Keycloak에서 confidential OIDC client를 만들고 다음 콜백을 허용합니다.

https://<ai-admin-host>/api/v1/auth/oidc/callback

시스템 설정 → 서비스 기본에서 서비스 Public URL을 먼저 확인한 뒤, Keycloak SSO에 issuer URL, Client ID, Client Secret, Scopes(최소 openid profile email), 관리자 역할 매핑을 넣습니다. issuer는 discovery 문서를 제공하고 컨테이너에서 닿아야 합니다. localhost가 아닌 HTTP issuer는 HTTP Issuer 허용을 켠 격리 개발망에서만 씁니다.

저장한 뒤 OIDC 연결 사전 진단 → 저장된 설정 진단을 실행하면 discovery, Authorization·Token·JWKS 엔드포인트, PKCE S256, Client ID·Secret, 콜백 URL, 관리자 역할 매핑을 한 번에 확인합니다. 진단은 의도적으로 무효한 인증 코드를 쓰므로 계정이나 세션을 만들지 않습니다.

권장 순서

  1. 로컬 Bootstrap 세션을 한 브라우저에 열어 둔 채로 진행합니다.
  2. 진단이 정상인지 확인하고, Keycloak의 Valid redirect URI와 화면의 콜백을 비교합니다.
  3. 별도 시크릿 창에서 SSO 로그인을 시험합니다.
  4. 새 OIDC 사용자의 생성·기본 역할·관리자 역할 매핑을 확인합니다.
  5. 실패하면 로그인 화면의 처리 단계추적 ID를 감사 로그에서 검색합니다.
  6. 필요하면 열어 둔 로컬 세션에서 SSO를 끄고 값을 고칩니다.

HTTPS로 서비스한다면 보안·세션 → HTTPS 전용 쿠키도 함께 켜세요.

3.4 AI 공급자와 모델

AI 관리 → 공급자·모델에서 등록합니다.

AI 공급자·모델 — 등록된 공급자 목록과 연결 시험 버튼

항목
공급자 유형 openai-compatible · ollama · custom
Base URL 내부망 엔드포인트
API 키 선택. 저장 시 암호화되며 목록·상세 응답에 원문이 나오지 않음
기본 모델 / 표시할 모델 목록 허용할 모델 이름
컨텍스트 · 최대 출력 토큰 각각 1 ~ 262,144
기본 스트리밍 요청에 지정이 없을 때의 기본값
요청 제한 시간 1 ~ 3,600초
사용 여부 끄면 요청에 쓰이지 않음

등록한 공급자는 AI 관리 → 스트리밍 플레이그라운드에서 바로 확인할 수 있습니다.

스트리밍 플레이그라운드 — 공급자·모델 선택과 요청 설정


4. 계정과 권한

4.1 기본 역할

역할·권한 — 네 기본 역할과 각 역할에 부여된 권한 수

역할 코드 화면 이름 할 수 있는 일
super_admin 서비스 관리자 서비스 전체 설정과 데이터. 전체 권한을 유지하도록 보호됨(축소 불가)
admin 운영 관리자 일상 운영, 콘텐츠, 통계, 사용자 조회 중심
team_lead 팀장 검토자 요청 검토와 승인·반려
user 일반 사용자 개인화 기능과 승인된 AI 기능

역할·권한 화면의 역할 탭에서 역할별 권한을 조정하고, 기능 권한 탭에서 권한 코드의 의미를 확인합니다. 변경은 저장 즉시 이후 요청의 서버 권한 검사에 반영됩니다.

권한을 나눌 때 지킬 것

4.2 계정 관리

관리자 사용자 — 인증 방식과 연결된 레거시 사용자

보안·권한 → 관리자 사용자에서 계정 상태(active, locked, disabled)와 역할을 바꿉니다. 지금 로그인한 자기 계정은 스스로 비활성화할 수 없습니다. 인증 방식은 로컬 또는 SSO로 표시되고, 연결된 레거시 사용자도 함께 보입니다.

4.3 API 키와 scope 정책

API 키 관리 — 사용자별 키 목록과 아래쪽 API 키 권한 정책

보안·권한 → API 키 관리에서 전체 사용자 키를, 사용자는 개인화 → 내 API 키에서 자기 키를 다룹니다. 발급 절차와 사용자 쪽 안내는 사용자 가이드에 있습니다.

4.4 검토·승인 흐름

승인은 두 스위치가 모두 켜져야 적용됩니다.

  1. 시스템 설정의 전역 workflow.enabled
  2. 검토·승인 → 프로세스 설정의 개별 정책 enabled

검토·승인 — 적용 중인 업무 수와 승인 대기 목록

기본 정책

코드 대상
api_key_privileged 관리자 전용 scope를 가진 키의 발급·회전
ai_provider_change AI 공급자 생성·수정·삭제
legacy_data_change 허용된 레거시 리소스 변경
legacy_runtime_config 레거시 Java 재시작형 설정 게시
general_review 별도 작업을 실행하지 않고 기록만 남기는 일반 운영 검토

앞의 네 실행형 정책은 대상 작업을 승인 대기로 바꾸고, 최종 승인 후 서버가 실제 작업을 실행합니다. 정책마다 승인 단계(1~3)와 검토 역할을 지정하며, 지정하지 않으면 요청은 승인 단계를 만들지 않고 즉시 우회됩니다.


5. 운영

5.1 상태 점검

경로 메서드 DB 필요 의미
/health/live GET · HEAD 아니요 프로세스가 HTTP 요청을 처리하는지
/health/ready GET · HEAD PostgreSQL ping까지 성공해 트래픽을 받을 준비가 됐는지
/api/v1/health/live GET · HEAD 아니요 위와 동일 (API 접두사 경로)
/api/v1/health/ready GET · HEAD 위와 동일 (API 접두사 경로)
/api/v1/meta GET · HEAD 아니요 빌드 버전·commit·빌드 시각
/api/v1/openapi.json GET · HEAD 아니요 OpenAPI 문서

ready를 readiness probe로, live를 liveness probe로 씁니다. 위 경로는 모두 HEAD도 받으므로 HEAD로 확인하는 가동 감시 도구나 로드 밸런서를 그대로 연결할 수 있습니다.

5.2 로그

Go 서버는 stdout에 JSON 구조 로그를 씁니다. HTTP 로그에는 method, path, status, byte 수, 소요 시간, request ID가 들어갑니다. 컨테이너 런타임에서 rotation과 보존 기간을 정하세요.

docker logs --tail 100 ai-admin
docker logs --since 30m ai-admin
docker logs --follow ai-admin

조사 목적이라도 민감정보를 로그로 복사하지 말고, 오류 응답의 내부 세부사항은 외부 티켓에 올리기 전에 가리세요.

5.3 감사

감사 로그 — 결과별 탭, 검색, CSV 내보내기

로그인·로그아웃, 설정·공급자·키·역할·사용자·승인 변경, 권한 거부와 AI 호출이 감사 이벤트로 남습니다. 보안·권한 → 감사 로그에서 검색어·결과·시작·종료 시각으로 거르고, audit.export 권한이 있으면 현재 필터를 그대로 CSV로 내보냅니다. SSO 실패는 처리 단계·실패 코드·HTTP 추적 ID를 별도 컬럼으로 보여 주므로 로그인 화면의 추적 ID로 바로 검색할 수 있습니다.

5.4 백업과 복구

반드시 함께 보호할 것

pg_dump --format=custom --schema=ai_admin --file=ai-admin-schema.dump "$POSTGRES_DSN"

위 명령은 셸 기록과 프로세스 목록에 DSN을 노출할 수 있습니다. 운영에서는 .pgpass, 단기 credential 또는 승인된 백업 에이전트를 쓰세요. 복구는 별도 DB에서 먼저 연습하고, 암호화 키가 맞는지 OIDC secret과 AI 공급자 연결 시험으로 확인합니다.

ENCRYPTION_KEY를 잃으면 저장된 비밀값을 복호화할 방법이 없습니다. OIDC client secret과 공급자 API 키를 모두 다시 입력해야 합니다.

5.5 업그레이드와 되돌리기

  1. 새 릴리즈의 tarball과 체크섬을 반입합니다.
  2. 기존 DB와 암호화 키를 백업합니다.
  3. staging 복제본에서 migration과 핵심 경로를 검증합니다.
  4. 새 이미지를 load하고 태그를 확인합니다.
  5. compose.offline.yml의 이미지 버전을 새 값으로 바꿉니다.
  6. 컨테이너를 재생성하고 readiness, 로그인, 설정 복호화, AI 스트리밍을 확인합니다.
sha256sum -c SHA256SUMS
gzip -dc ai-admin-v1.2.19.tar.gz | docker load
docker image inspect ai-admin:v1.2.19 --format ''
docker compose --env-file .env -f compose.offline.yml up -d --force-recreate

되돌리는 법: migration은 시작할 때 전진 적용만 하며 자동 down migration은 없습니다. downgrade하려면 이전 이미지뿐 아니라 그 버전과 호환되는 DB 백업도 함께 준비해야 합니다. 이전 이미지만 되돌리면 새 스키마 위에서 뜨게 되므로 안전하지 않습니다.

종료는 다음과 같이 합니다. SIGTERM을 받으면 새 요청을 중단하고 최대 20초 동안 정상 종료를 시도합니다. 같은 명령으로 PostgreSQL까지 내리지 마세요.

docker compose -f compose.offline.yml stop ai-admin

5.6 정기 점검

작업 모니터링 메뉴로 레거시 쪽 작업 실패도 함께 봅니다.

배치 오류 모니터링 — 부서·프로젝트별 실패 이력


6. 장애 대응

컨테이너가 뜨자마자 죽는다

docker logs ai-admin의 첫 줄을 봅니다.

로그 메시지 확인할 것
configuration rejected 네 환경 변수 누락, 12자 미만 Bootstrap 비밀번호, 잘못된 Base64 또는 32바이트가 아닌 암호화 키
database startup failed DNS, 라우팅, PostgreSQL 계정, TLS/DSN
database migration failed ai_admin 스키마 DDL 권한, 이전에 실패한 트랜잭션
database bootstrap failed unique 제약, 역할 seed, bcrypt 처리 오류

같은 아이디의 비로컬(OIDC) 계정과 BOOTSTRAP_ADMIN이 충돌해도 시작을 거부합니다.

live는 되는데 ready가 503

DB 연결 문제입니다. 풀은 최대 20 · 최소 2 연결을 씁니다. PostgreSQL의 연결 한도와 네트워크 정책을 확인하세요.

로그인이 안 된다

저장된 비밀값을 쓸 수 없다

최근 배포에서 ENCRYPTION_KEY가 바뀌지 않았는지 확인합니다. 원래 키를 복구할 수 없으면 복호화가 불가능하므로 OIDC client secret과 공급자 API 키를 다시 입력해야 합니다.

AI 응답이 중간에 끊긴다

사용자에게는 AI 응답이 완료되기 전에 연결이 끊어졌습니다.가 보입니다. 서버는 끝까지 전달하지 못한 중계를 감사에 failure와 사유(upstream_read_failed, upstream_timeout 등)로 남기고 응답을 중단합니다. 확인 순서는 다음과 같습니다.

레거시 설정을 저장했는데 Java 동작이 그대로다

레거시 값이 [MASKED]로 보인다

민감 필드의 정상 기본 동작입니다. 일반 관제·참조 확인에는 마스킹 상태를 그대로 쓰세요. 원문 확인이 업무상 꼭 필요하면 보안 승인 후 기간을 제한한 legacy.sensitive.read 역할을 부여하고 작업 직후 회수한 다음, 감사 로그의 legacy.sensitive.read 이벤트를 확인합니다. 도메인 allowlist에서 제외된 원문은 이 권한으로도 반환되지 않습니다.

목록 저장이 409 충돌로 거부된다

레거시 관리 화면의 수정·삭제는 목록에서 읽은 32자 _rowVersion으로 동시 변경을 확인합니다. 제출을 반복하지 말고 새로고침한 뒤 최신 row를 검토하세요. AI 공급자 화면도 같은 원리로 updatedAt을 비교합니다.

새로고침하면 404가 난다

리버스 프록시가 SPA fallback을 ai-admin으로 전달하는지 확인합니다. 서버는 알 수 없는 경로를 SPA로 넘겨주므로, 프록시가 먼저 404를 만들고 있는지 봅니다.


7. 보안

7.1 배포 직후 바꿔야 하는 기본값

항목 기본값 조치
BOOTSTRAP_ADMIN_PASSWORD 없음(직접 지정) .env.example의 예시 문자열을 그대로 쓰지 말고 12자 이상 무작위 값으로
ENCRYPTION_KEY 없음(직접 지정) openssl rand -base64 32로 새로 만들고 별도 보관
보안·세션 → HTTPS 전용 쿠키 꺼짐 HTTPS 운영이면 켠다
Keycloak SSO → HTTP Issuer 허용 꺼짐 운영에서는 끈 채로 둔다
승인 전체 설정 꺼짐 고권한 키·공급자 변경에 검토가 필요하면 켠다
세션 유효 시간 480분 조직 정책에 맞게 조정(15~10080분)

7.2 외부에 열면 안 되는 것

7.3 인증과 세션

7.4 레거시 데이터 취급

전체 보안 모델은 보안 가이드에 있습니다.


화면 캡처를 다시 만들 때

이 문서와 사용자 가이드의 캡처는 저장소의 캡처 도구로 만듭니다. 운영 DB가 아닌 합성 DB로 띄운 ai-admin만 대상으로 삼으세요. 도구는 기본적으로 localhost만 허용하고 SCREENSHOT_DATA_MODE=synthetic 확인이 없으면 시작하지 않으며, 화면에 오류가 하나라도 있으면 결과를 남기지 않고 실패합니다. 자세한 사용법과 생성 파일 목록은 docs/screenshots/README.md에 있습니다.


함께 보기