본문으로 건너뛰기

3.1. OPENMARU COP 운영절차


OPENMARU COP Console 개요

CLI(kubectl/helm)로 수행하는 대부분의 작업은 OPENMARU COP Console(웹 콘솔)에서도 동일하게 수행할 수 있습니다. 본 장의 각 절차에서는 CLI 명령과 함께 해당 작업에 대응하는 Console 화면을 함께 안내합니다.

Console 전체 레이아웃/사이드바

Console은 https://console.{sub_domain}.{domain}.{TLD} 주소로 접속하며, SSO 로그인(Keycloak 통합 인증) 또는 토큰 로그인(ServiceAccount 토큰) 두 가지 방식을 지원합니다.

Console 로그인 화면
Keycloak SSO 로그인 화면

로그인 후에는 사용자 권한(admins/users/viewers)에 따라 표시되는 메뉴와 수행 가능한 작업이 달라집니다. 여러 클러스터가 등록된 환경에서는 상단 헤더의 클러스터 선택 드롭다운으로 클러스터를 전환할 수 있고, 네임스페이스 드롭다운으로 작업 대상 네임스페이스를 전환할 수 있습니다. 로그아웃은 우측 상단 프로필 메뉴에서 수행합니다.


사용자 관리

OPENMARU COPLLDAP(경량 사용자 디렉터리)과 Keycloak(SSO/OIDC)을 통해 사용자 계정을 통합 관리합니다.

LLDAP 접속 정보

항목
네임스페이스openmaru-sso
LDAP 포트3890
Web UI 포트17170
Web UI URLhttps://ldap.{sub_domain}.{domain}.{TLD}
Base DNdc={sub_domain},dc={domain},dc={TLD}

ℹ️ 권장: 사용자/그룹 관리는 아래 LLDAP 웹 UI를 기본으로 사용하십시오. CLI(lldap-cli)는 스크립트 자동화가 필요한 경우에만 보조적으로 사용합니다.

방법 1) LLDAP 웹 UI를 통한 사용자 생성

  1. LLDAP Web UI(https://ldap.{sub_domain}.{domain}.{TLD})에 관리자 계정으로 로그인합니다.
  2. Users > Create a user 메뉴에서 사용자명, 이메일, 비밀번호를 입력합니다.
LLDAP Users 목록
LLDAP 사용자 생성 화면
  1. Groups 메뉴에서 사용자를 아래 기본 그룹 중 하나에 소속시킵니다. 그룹 상세 화면 하단의 드롭다운에서 사용자를 선택하고 Add to group 버튼을 누르면 됩니다.
그룹매핑되는 클러스터 권한
adminscluster-admin (전체 관리 권한)
userscop-cluster-users (편집 사용자)
viewerscop-cluster-viewers (조회 사용자)
LLDAP 그룹 상세 - 멤버 추가

방법 2) lldap-cli를 통한 사용자 생성

# 사용자 생성
lldap-cli user add --username jdoe --email jdoe@example.com

# 비밀번호 설정
lldap-cli user set-password --username jdoe

# 그룹에 사용자 추가
lldap-cli group add-member --group users --username jdoe

# 사용자 목록 조회
lldap-cli user list

# 사용자 상세 조회
lldap-cli user show --username jdoe

그룹 관리

# 그룹 생성
lldap-cli group add --name developers

# 그룹에 사용자 추가/제거
lldap-cli group add-user --group developers --username jdoe
lldap-cli group remove-user --group developers --username jdoe

# 그룹 목록/상세 조회
lldap-cli group list
lldap-cli group show --name developers

Keycloak 동기화

LLDAP에 계정을 생성한 후에는 Keycloak과 동기화해야 실제 로그인이 가능합니다. Keycloak Admin Console(https://sso.{sub_domain}.{domain}.{TLD})에서 대상 Realm(openmaru)을 선택한 뒤 User federation > ldap로 진입하여 우측 상단 Action 드롭다운에서 Sync all users를 실행하거나, 사용자가 최초 로그인을 시도하면 자동으로 동기화됩니다.

Keycloak User federation - LDAP Provider 목록
Keycloak LDAP Provider Action - Sync all users

⚠️ 주의: 신규 사용자를 생성한 직후 즉시 로그인이 안 될 경우, Keycloak Full Sync를 수동으로 실행하십시오.


사용자 권한 부여

OPENMARU COP는 Kubernetes RBAC(Role-Based Access Control)과 OIDC 그룹 클레임을 연동하여 권한을 부여합니다.

그룹 - 역할 매핑

LLDAP 그룹OIDC 그룹 클레임ClusterRole
adminsoidc:adminscluster-admin
usersoidc:userscop-cluster-users (커스텀)
viewersoidc:viewerscop-cluster-viewers (커스텀)

커스텀 ClusterRoleBinding 예시

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: oidc-admins-binding
subjects:
- kind: Group
name: "oidc:admins"
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: cluster-admin
apiGroup: rbac.authorization.k8s.io

네임스페이스 단위 권한 부여(Role/RoleBinding)

특정 사용자/그룹에게 특정 네임스페이스에 대한 제한된 권한만 부여하려면 Role과 RoleBinding을 사용합니다.

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: app-developer
namespace: my-app
rules:
- apiGroups: ["", "apps"]
resources: ["pods", "deployments", "services", "configmaps"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: app-developer-binding
namespace: my-app
subjects:
- kind: Group
name: "oidc:developers"
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: app-developer
apiGroup: rbac.authorization.k8s.io

권한 확인

⚠️ 주의: OPENMARU COP는 OIDC 사용자명에 oidc: 접두사(--oidc-username-prefix=oidc:)를 붙이도록 설정되어 있습니다. --as 옵션에 접두사 없이 사용자명만 입력하면 존재하지 않는 사용자로 조회되어 결과가 부정확합니다.

# 특정 사용자의 특정 네임스페이스 내 전체 권한 목록 확인
kubectl auth can-i --list --as=oidc:jdoe@example.com -n my-app

# 특정 동작 수행 가능 여부 확인
kubectl auth can-i create deployments --as=oidc:jdoe@example.com -n my-app

# OIDC 그룹에 연결된 ClusterRoleBinding 확인
kubectl get clusterrolebindings | grep oidc

Console에서 확인

좌측 사용자 관리 메뉴에서 서비스 계정, 역할(Role/ClusterRole), 역할 바인딩(RoleBinding/ClusterRoleBinding)을 GUI로 조회할 수 있습니다. 역할 상세 화면에서는 Resources/Verbs/API Groups 규칙을, 역할 바인딩 상세 화면에서는 연결된 Subject(사용자/그룹) 목록을 확인할 수 있습니다.

역할 목록
역할 바인딩 목록

프로젝트(네임스페이스) 생성

OPENMARU COP에서 프로젝트는 Kubernetes Namespace로 구현됩니다.

방법 1) OPENMARU COP Console을 통한 생성

  1. 좌측 메뉴에서 클러스터 > 네임스페이스로 이동합니다.
  2. Create 버튼을 클릭합니다.
  3. Name, Labels, Annotations를 입력하고 생성합니다.
네임스페이스 목록
네임스페이스 상세

ℹ️ 생성된 리소스 간의 소유/연결 관계는 Console 메뉴에서 시각적 그래프로 확인할 수 있습니다.

방법 2) CLI를 통한 생성

kubectl create namespace my-app

리소스 쿼터 설정

신규 프로젝트 생성 시 리소스 사용량 제한을 함께 설정하는 것을 권장합니다.

apiVersion: v1
kind: ResourceQuota
metadata:
name: my-app-quota
namespace: my-app
spec:
hard:
requests.cpu: "8"
requests.memory: 16Gi
limits.cpu: "16"
limits.memory: 32Gi
pods: "50"
services: "20"
secrets: "50"
configmaps: "50"
persistentvolumeclaims: "10"

CIS 보안 수준(Pod Security Standards) 지정

네임스페이스 단위로 Pod 보안 수준을 지정할 수 있습니다.

apiVersion: v1
kind: Namespace
metadata:
name: my-app
labels:
pod-security.kubernetes.io/enforce: restricted
보안 수준설명
privileged제한 없음
baseline알려진 권한 상승 차단
restricted최고 수준의 보안 제약 적용(운영 환경 권장)

Jenkins를 통한 신규 애플리케이션 프로젝트 생성 (자동화)

CI/CD 파이프라인과 함께 신규 애플리케이션을 배포하는 경우, Jenkins의 00-NEW-PROJECT Job을 통해 네임스페이스 생성부터 Deployment/Service/Ingress 생성, 후속 빌드/배포 Job 생성까지 자동화할 수 있습니다.

Job 파라미터설명
NAMESPACE생성할 네임스페이스명
DEPLOYMENT생성할 Deployment명
GIT_URL애플리케이션 소스 저장소 URL
BASE_IMAGE사용할 S2I 빌더 이미지
HOSTIngress에 연결할 도메인
NEXUS_URL아티팩트 저장소 URL

오토스케일링(HPA/CronHPA) 설정

OPENMARU COP는 표준 Kubernetes HPA(Horizontal Pod Autoscaler)와 함께, 시간 예약 기반의 자체 오토스케일러인 CronHPA를 제공합니다.

표준 HPA 설정

kubectl autoscale deployment my-app --cpu-percent=70 --min=2 --max=10 -n my-app

CronHPA 설정

CronHPA는 6자리(초 단위 포함) 크론 표현식을 사용하며, 네임스페이스 openmaru-cronhpa에서 컨트롤러가 동작합니다.

apiVersion: autoscaling.openmaru.io/v1
kind: CronHPA
metadata:
name: my-app-cronhpa
namespace: my-app
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: my-app
jobs:
- name: "scale-up-business-hours"
schedule: "0 0 9 * * *"
targetSize: 5
- name: "scale-down-off-hours"
schedule: "0 0 20 * * *"
targetSize: 1
스펙 필드설명
scaleTargetRef스케일 대상 리소스(Deployment 등)
jobs[].schedule초 분 시 일 월 요일 순서의 6자리 크론 표현식
jobs[].targetSize지정 시각에 적용할 Replica 수
jobs[].runOnce1회성 실행 여부
excludeDates스케줄링을 제외할 날짜 목록

확인 및 문제 해결

# CronHPA 리소스 조회
kubectl get cronhpa -A
kubectl describe cronhpa -n my-app my-app-cronhpa

# 컨트롤러 로그 확인
kubectl logs -n openmaru-cronhpa deployment/openmaru-cop-cronhpa

⚠️ 주의: 표준 HPA와 CronHPA를 동일 Deployment에 함께 적용할 경우 두 컨트롤러가 서로 다른 시점에 충돌할 수 있으므로, CronHPA의 targetSize는 HPA의 minReplicas를 조정하는 방식으로 연동하는 것을 권장합니다.

Console에서 확인

워크로드 > HPA / CronHPA 메뉴에서 설정된 오토스케일러 목록과 현재 Replica 상태를 조회할 수 있습니다.

HPA 목록
CronHPA 목록

PVC(스토리지) 생성

OPENMARU COP는 두 가지 StorageClass를 기본 제공합니다.

StorageClass접근 모드볼륨 확장용도
nfs-client (기본)RWO, RWX지원여러 Pod 간 공유가 필요한 데이터
local-pathRWO미지원단일 Pod 전용, 고성능이 필요한 데이터

PVC 생성 예시

# NFS 기반 공유 볼륨(RWX)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-app-shared-data
namespace: my-app
spec:
accessModes: ["ReadWriteMany"]
storageClassName: nfs-client
resources:
requests:
storage: 10Gi
---
# Local Path 기반 전용 볼륨(RWO)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-app-local-data
namespace: my-app
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: local-path
resources:
requests:
storage: 20Gi

볼륨 확장

kubectl patch pvc my-app-shared-data -n my-app \
-p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}'

ℹ️ 참고: local-path StorageClass는 볼륨 확장을 지원하지 않으므로, 확장이 예상되는 데이터는 nfs-client를 사용하십시오.

Console에서 확인

스토리지 > 영구 볼륨 클레임(PVC) 메뉴에서 상태(Bound/Pending/Lost)를 색상으로 확인할 수 있습니다.

PVC 목록

애플리케이션 빌드/배포

ℹ️ 참고: 소스코드 빌드는 S2I(Source-to-Image) CLI로 수행하며, 이를 Jenkins Job으로 자동화합니다. 상세 절차는 COP 빌드/배포 가이드도 함께 참고하십시오.

S2I(Source-to-Image) 빌드

s2i build <소스_위치> <빌더_이미지> <결과_이미지>

# 예시: Maven 프로젝트를 Nexus Mirror를 사용하여 빌드(Tomcat9 + JDK17)
s2i build ${GIT_URL} registry.{sub_domain}.{domain}.{TLD}:8443/images/tomcat9-jdk17-ubi8-s2i-openmaru:latest \
registry.{sub_domain}.{domain}.{TLD}:8443/apps/my-app:1.0.0 \
-e MAVEN_MIRROR_URL=${NEXUS_URL} \
-e CPU_LIMIT=2000m \
-e MEMORY_LIMIT=1Gi

지원 빌더 이미지: OpenJDK 8/11/17/21, Tomcat9+JDK 8/11/17, Node.js 18/20/22, Python 3.9/3.11/3.12, PHP 8.2/8.3, Ruby 3.3, Nginx 1.26, HTTPD 2.4, Varnish 6, MariaDB/MySQL/PostgreSQL/Redis 등. 전체 목록은 4.2. 자주 사용하는 명령어 > Private Registry 관리 참고.

Jenkins Job / Bastion 스크립트를 통한 빌드-배포 자동화

Bastion 서버의 애플리케이션 작업 디렉터리에서 제공되는 스크립트를 실행하면 S2I 빌드부터 배포까지 한 번에 처리할 수 있습니다. 동일한 동작이 Jenkins Job(<namespace>-<app>-build<namespace>-<app>-deploy)으로도 자동화되어 있습니다.

# Bastion에서 직접 실행하는 경우
cd /data/workspaces/apps/<namespace>/<app>
git -C git pull origin main
./build-app.sh # S2I 빌드(소스 → 컨테이너 이미지)
./build-push.sh # Harbor Registry로 이미지 Push
./build-deploy.sh # Deployment 이미지 업데이트 및 롤아웃
# 실행 로그 예시
>> Image Tag : main-4375493
>> Start build : my-app in my-app...
[INFO] BUILD SUCCESS
>> Image push completed: registry.{sub_domain}.{domain}.{TLD}:8443/apps/my-app:main-4375493
>> Start deploy : my-app in my-app
deployment.apps/my-app image updated
>> Waiting for rollout to complete...
deployment "my-app" successfully rolled out

Jenkins에서 실행할 경우 Jenkins > <namespace>-<app>-build(S2I 빌드+Push) Job을 먼저 실행한 뒤 <namespace>-<app>-deploy Job으로 배포합니다. Job 체계는 아래 CI/CD 파이프라인을 통한 배포 절을 참고하십시오.

Console에서 확인

배포 완료 후 워크로드 > Deployment 메뉴에서 롤아웃 상태와 리소스 사용량을 확인할 수 있습니다. 상세 화면에서 CPU/Memory 요청·제한 값을 직접 조정하거나(리소스 제한 편집), 로그·터미널을 분할 뷰로 함께 열어볼 수 있습니다.

Deployment 목록
Deployment 상세
컨테이너 리소스 제한 편집

Helm을 통한 배포

helm repo add openmaru http://chartmuseum.{sub_domain}.{domain}.{TLD}:8181
helm repo update

helm install my-app openmaru/my-app-chart \
--create-namespace -n my-app \
-f values.yaml

helm upgrade my-app openmaru/my-app-chart -n my-app -f values.yaml
helm rollback my-app 1 -n my-app
helm list -A
helm status my-app -n my-app
helm history my-app -n my-app
helm uninstall my-app -n my-app

CI/CD 파이프라인을 통한 배포 (GitLab → Jenkins → Harbor/Nexus → ArgoCD)

OPENMARU COP의 표준 CI/CD 파이프라인은 다음과 같은 Jenkins Job 번호 체계를 따릅니다.

Job역할
00-NEW-PROJECT신규 프로젝트/네임스페이스 생성
10-*-helm-installHelm Chart 최초 설치
20-*-buildS2I 빌드 및 이미지 Push
30-*-deploy이미지 배포
40-*-rollback이전 버전 롤백
50-*-blue-greenBlue-Green 배포 전환
60-*-hpa오토스케일링 설정
70-*-argocd-deployArgoCD GitOps 배포
99-*-helm-uninstall리소스 삭제

Blue-Green 배포 전환 예시:

kubectl patch service my-app -n my-app \
-p '{"spec":{"selector":{"deployment":"green"}}}'

ArgoCD를 통한 GitOps 배포 확인:

argocd app list --grpc-web
argocd app get my-app --grpc-web
argocd app sync my-app --grpc-web
argocd app history my-app --grpc-web

이미지 태그 업데이트(수동 배포)

kubectl set image deployment/my-app my-app=registry.{sub_domain}.{domain}.{TLD}:8443/apps/my-app:v1.1 -n my-app
kubectl rollout status deployment/my-app -n my-app

보안 컨텍스트(CIS 프로파일 준수)

운영 환경(CIS restricted 네임스페이스) 배포 시 다음 SecurityContext 항목을 반드시 준수해야 합니다. runAsUser 값은 S2I 빌더 이미지 종류에 따라 다르므로 임의로 고정하지 말고 아래 표를 따라야 합니다. 값이 실제 이미지 UID와 다르면 파일 쓰기 권한 오류 등으로 Pod가 기동에 실패할 수 있습니다.

S2I 이미지 계열runAsUser비고
Java/Tomcat(OpenJDK, Tomcat9)185SCL OpenJDK/Tomcat 표준 UID
기타(Node.js, Python, PHP, Ruby, Nginx, HTTPD)1001SCL(Software Collections) 표준 UID
# 예시: Java/Tomcat 기반 S2I 이미지
securityContext:
runAsNonRoot: true
runAsUser: 185 # Java/Tomcat 계열. 그 외 런타임은 1001
runAsGroup: 0 # root 그룹(S2I 이미지 표준)
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
seccompProfile:
type: RuntimeDefault
capabilities:
drop: ["ALL"]

Service/NodePort 설정

Service 타입사용 예
ClusterIP클러스터 내부 통신 전용(기본값)
NodePort클러스터 외부에서 노드 IP + 포트로 직접 접근
LoadBalancer외부 로드밸런서 연동(온프레미스 환경에서는 제한적으로 사용)
apiVersion: v1
kind: Service
metadata:
name: my-app-nodeport
namespace: my-app
spec:
type: NodePort
selector:
app: my-app
ports:
- port: 8080
targetPort: 8080
nodePort: 30080

ℹ️ 참고: NodePort 사용 가능 범위는 30000-32767입니다. 운영 환경에서는 가급적 NodePort보다 Ingress를 사용하는 것을 권장합니다.

Console에서 확인

네트워크 > 서비스 메뉴에서 Service 목록과 연결된 Endpoint(Pod) 상태를 조회할 수 있습니다.

서비스 목록

NetworkPolicy 설정

기본적으로 모든 Pod 간 통신이 허용되어 있으므로, 운영 환경에서는 네임스페이스 단위로 NetworkPolicy를 적용하여 트래픽을 제한하는 것을 권장합니다.

# 기본적으로 모든 인바운드/아웃바운드 트래픽 차단
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: my-app
spec:
podSelector: {}
policyTypes: ["Ingress", "Egress"]
---
# 동일 네임스페이스 내 통신만 허용
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-same-namespace
namespace: my-app
spec:
podSelector: {}
ingress:
- from:
- podSelector: {}
---
# DNS(53번 포트) 통신 허용
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns
namespace: my-app
spec:
podSelector: {}
egress:
- to: []
ports:
- protocol: UDP
port: 53

ℹ️ 참고: OPENMARU COP는 프로젝트/노드 단위 고정 Egress IP 기능을 기본 제공하지 않습니다. 특정 프로젝트의 외부 통신에 고정 출발지 IP가 필요한 경우, 별도의 Egress Gateway(NAT 전용 노드) 구성을 통해 아키텍처 수준에서 지원해야 하며, 도입이 필요할 경우 기술 지원팀과 협의하시기 바랍니다.

Console에서 확인

네트워크 > 네트워크 정책 메뉴에서 적용된 NetworkPolicy의 from/to/ports 규칙을 시각적으로 확인할 수 있습니다.

네트워크 정책 목록

Helm Chart(템플릿) 수정

OPENMARU COPHelm Chart를 애플리케이션 배포 템플릿으로 사용합니다.

# 새 Chart 스캐폴딩 생성
helm create my-app-chart

# Chart 문법 검증
helm lint my-app-chart

# Chart 패키징
helm package my-app-chart

# 사내 ChartMuseum(openmaru repo)에 업로드
helm cm-push my-app-chart-1.0.0.tgz openmaru

values.yaml을 수정한 뒤에는 helm upgrade로 반영합니다.

helm upgrade my-app openmaru/my-app-chart -n my-app -f values.yaml --dry-run
helm upgrade my-app openmaru/my-app-chart -n my-app -f values.yaml

Console에서 확인

Helm 차트 > 릴리스 메뉴에서 설치된 Release 목록과 상세(Summary/Values/Manifests/Notes/Resources/History 탭)를 확인하고, 업그레이드/롤백/삭제를 GUI로 수행할 수 있습니다. 저장소 메뉴에서는 ChartMuseum 등 Helm Repository를 등록/관리합니다.

Helm 릴리스 목록

Worker/Infra 노드 증설

  1. 신규 노드 호스트명 설정

    hostnamectl hostname cop-worker-3
  2. inventory.inienv.yaml에 신규 노드 정보 추가

  3. Bastion에서 신규 노드로 SSH 키 배포

    ./run-sshkeycopy.sh <신규_노드_IP>
  4. 신규 노드 기본 설정 적용

    ./run-playbook.sh openmaru-cop-system-setup.yaml --limit cop-worker-3
    ./run-playbook.sh openmaru-cop-system-dns.yaml --limit cop-worker-3
  5. (GPU 노드인 경우) GPU 드라이버 사전 설치

    ./120-gpu-preinstall.sh
    ./140-gpu-install.sh
  6. RKE2 설치 및 클러스터 조인

    ./run-playbook.sh openmaru-cop-rke2-install.yaml
    ./run-playbook.sh openmaru-cop-rke2-post-install.yaml --limit cop-worker-3
    ./run-playbook.sh openmaru-cop-rke2-private-registry.yaml --limit cop-worker-3
  7. (GPU 노드인 경우) GPU Operator 설치

    ./450-gpu-operator-install.sh

노드 제거 절차

kubectl cordon cop-worker-3
kubectl drain cop-worker-3 --ignore-daemonsets --delete-emptydir-data

# 대상 노드에서 실행
systemctl stop rke2-agent
systemctl disable rke2-agent

# Bastion 또는 Master 노드에서 실행
kubectl delete node cop-worker-3

# 대상 노드에서 실행 (RKE2 완전 제거)
rke2-uninstall.sh

⚠️ 주의: 노드 제거 전 반드시 Cordon/Drain을 수행하여 해당 노드의 워크로드가 다른 정상 노드로 안전하게 이전(재스케줄링)되도록 해야 합니다.

Console에서 확인

클러스터 > 노드 메뉴에서 노드 목록/상세(사양, 할당 리소스, Conditions)를 조회할 수 있으며, Cordon/Uncordon과 Drain을 GUI 버튼으로 수행할 수 있습니다. Node Shell 기능을 사용하면 디버그용 특권 Pod를 통해 브라우저에서 노드에 직접 터미널로 접속할 수 있습니다.

노드 목록
노드 상세

Private Registry 관리

OPENMARU COP는 컨테이너 이미지 저장소로 Harbor를, 빌드 아티팩트 저장소로 Nexus를 사용합니다.

Harbor 로그인 및 이미지 Push/Pull

docker login registry.{sub_domain}.{domain}.{TLD}:8443 -u admin -p ${HARBOR_PASSWORD}

docker tag my-app:v1.0 registry.{sub_domain}.{domain}.{TLD}:8443/apps/my-app:v1.0
docker push registry.{sub_domain}.{domain}.{TLD}:8443/apps/my-app:v1.0
docker pull registry.{sub_domain}.{domain}.{TLD}:8443/apps/my-app:v1.0

Harbor 프로젝트 구조

프로젝트공개 여부용도
libraryPublic공용 베이스 이미지
imagesPublicS2I 빌더 이미지
appsPrivate고객사 애플리케이션 이미지

ImagePullSecret 생성

kubectl create secret docker-registry harbor-secret \
--docker-server=registry.{sub_domain}.{domain}.{TLD}:8443 \
--docker-username=admin \
--docker-password=${HARBOR_PASSWORD} \
-n my-app

이미지 취약점 스캔

Harbor는 내장된 Trivy 스캐너로 Push 시 자동 취약점 스캔을 지원합니다. 프로젝트 설정에서 Automatically scan on push, **Vulnerability severity threshold(Critical)**를 지정할 수 있습니다.

가비지 컬렉션

Harbor 관리 콘솔의 Administration > Garbage Collection에서 미사용(Untagged) 이미지의 정기 삭제 스케줄(예: 매일)을 설정할 수 있습니다.

ℹ️ 참고: OPENMARU COP Console의 도구 메뉴에서 Harbor를 비롯한 GitLab/Jenkins/ArgoCD/Nexus 등 통합 DevOps 도구로 바로 이동할 수 있습니다.


Worker 노드 Graceful 종료

계획된 점검, OS 패치 등으로 특정 Worker 노드를 안전하게 종료해야 할 때는 다음 3단계 절차를 따릅니다.

단계명령효과
1. Cordonkubectl cordon <node-name>신규 Pod 스케줄링 차단(기존 Pod는 유지)
2. Drainkubectl drain <node-name> --ignore-daemonsets --delete-emptydir-data기존 Pod를 다른 노드로 안전하게 재배치
3. Uncordon(작업 완료 후)kubectl uncordon <node-name>스케줄링 재개
# 1단계: 스케줄링 차단
kubectl cordon cop-worker-2

# 2단계: 워크로드 안전 이전(최대 300초 대기, 필요 시 --force)
kubectl drain cop-worker-2 --ignore-daemonsets --delete-emptydir-data --timeout=300s

# 점검 작업 수행 (OS 패치, 재부팅 등)

# 3단계: 점검 완료 후 스케줄링 재개
kubectl uncordon cop-worker-2
시나리오권장 절차
OS 패치 적용Cordon → Drain → 패치 → 재시작 → Uncordon
긴급 점검Cordon → Drain → 점검 → Uncordon
하드웨어 교체Cordon → Drain → 노드 제거(Worker/Infra 노드 증설 참고)
부하 분산 재조정Cordon(신규 스케줄링만 차단)

여러 노드를 순차적으로 점검해야 하는 경우, Ansible 자동화 플레이북을 사용하면 Cordon/Drain을 포함한 롤링 재시작을 일괄 수행할 수 있습니다.

./run-playbook.sh openmaru-cop-rke2-rolling-restart.yaml \
-e "target_group=workers" \
-e "cordon_nodes=true" \
-e "drain_nodes=true"

GPU 자원 운영

GPU 노드가 있는 환경에서 GPU 할당 상태를 확인하고, 여러 워크로드가 한 장을 나누어 쓰도록 운영하는 절차입니다.

GPU 할당 방식

COP 1.0.3부터 DRA(Dynamic Resource Allocation) 가 기본 방식입니다. 워크로드가 nvidia.com/gpu 확장 리소스를 직접 요청하는 대신 ResourceClaim 으로 장치를 요청합니다.

방식요청광고 주체프로젝트 간 공유
DRA (기본)ResourceClaimDRA 드라이버가능
타임슬라이싱nvidia.com/gpudevice-plugin노드 단위로 동작

주의: 두 방식을 함께 쓰지 마십시오. 같은 GPU를 두 경로가 각각 배정하면 서로의 점유를 알지 못해 실제 사용량이 물리 용량을 넘어섭니다.

현재 상태 확인

# 노드별 물리 GPU 목록 — 드라이버가 발행합니다
kubectl get resourceslices

# 각 장치의 상세 (공유 가능 여부·용량)
kubectl get resourceslices -o custom-columns=\
NODE:.spec.nodeName,DRIVER:.spec.driver,\
SHARED:.spec.devices[*].allowMultipleAllocations

# 어느 워크로드가 GPU를 잡고 있는지
kubectl get resourceclaims -A

참고: 노드 화면의 GPU 개수와 resourceslices 의 장치 수는 다를 수 있습니다. 타임슬라이싱을 켜면 노드에는 물리 장수 × replicas 가 표시되지만 resourceslices 에는 물리 장치 수만 나옵니다. 실제 장비 수를 확인할 때는 후자를 봅니다.

GPU 사용량 확인

# GPU를 사용 중인 Pod 안에서 확인합니다
kubectl exec -n <네임스페이스> <pod> -- \
nvidia-smi --query-gpu=name,memory.total,memory.used,memory.free --format=csv

주의: GPU 메모리는 스케줄러가 계산만 할 뿐 물리적으로 나뉘지 않습니다. 한 워크로드가 메모리를 모두 사용하면 같은 GPU를 쓰는 다른 워크로드가 종료됩니다. 함께 실행할 워크로드의 메모리 합계가 물리 용량을 넘지 않도록 관리해야 합니다.

여러 워크로드가 GPU 한 장을 나누어 쓰기

ResourceClaim을 미리 만들고 여러 Pod가 그 이름을 참조하는 방식입니다.

apiVersion: resource.k8s.io/v1
kind: ResourceClaim
metadata:
name: shared-gpu
namespace: <네임스페이스>
spec:
devices:
requests:
- name: gpu
exactly:
deviceClassName: gpu.nvidia.com
allocationMode: ExactCount
count: 1

Pod 쪽에서는 이 클레임을 이름으로 참조합니다.

spec:
resourceClaims:
- name: gpu
resourceClaimName: shared-gpu # 템플릿이 아니라 이름 참조
containers:
- name: app
resources:
claims:
- name: gpu

주의: resourceClaimTemplateName 을 쓰면 Pod마다 클레임이 새로 생겨 장치를 각각 점유합니다. GPU가 한 장인 노드에서는 두 번째 Pod가 Pending 이 됩니다. 공유가 목적이면 반드시 이름 참조를 사용합니다.

클레임이 어느 Pod에 배정됐는지는 다음으로 확인합니다.

kubectl get resourceclaim -n <네임스페이스> shared-gpu \
-o jsonpath='{range .status.reservedFor[*]}{.name}{"\n"}{end}'

프로젝트 간 GPU 공유

장치 분할 설정이 켜져 있으면 서로 다른 프로젝트(네임스페이스)의 워크로드도 같은 GPU를 사용할 수 있습니다. ResourceClaim은 프로젝트 단위 오브젝트라 각각 따로 만들지만, 같은 물리 장치에 배정됩니다.

# 장치가 다중 할당을 허용하는지 — True 여야 합니다
kubectl get resourceslices \
-o jsonpath='{range .items[*]}{.spec.nodeName}{" "}{.spec.devices[*].allowMultipleAllocations}{"\n"}{end}'

# 값이 비어 있으면 클러스터 기능 게이트를 확인합니다 (1 이면 켜진 상태)
kubectl get --raw /metrics | grep DRAConsumableCapacity

GPU 노드 점검

GPU 노드도 Worker 노드 Graceful 종료와 같은 절차를 따릅니다. 다만 다음을 먼저 확인합니다.

확인이유
진행 중인 GPU 작업학습·추론 작업은 중간에 끊기면 처음부터 다시 해야 할 수 있습니다
다른 GPU 노드의 여유Drain 후 재배치될 자리가 있어야 합니다. 없으면 Pending 으로 남습니다
# 해당 노드에서 GPU를 쓰는 Pod 확인
kubectl get pods -A --field-selector spec.nodeName=<노드> -o json \
| jq -r '.items[] | select(.spec.resourceClaims != null) | "\(.metadata.namespace)/\(.metadata.name)"'

GPU 드라이버 버전 상향

드라이버는 이미 설치되어 있으면 건너뜁니다. 버전만 올려서는 노드가 갱신되지 않으므로 제거 후 재설치해야 합니다. 절차는 설치 가이드의 GPU 환경 설정 장을 참조하십시오.