Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
name: Build and Push Image

on:
workflow_dispatch:
inputs:
github_tag:
description: 'GitHub tag / Image tag'
required: true
type: string

jobs:
build-and-push:
name: Build and Push
runs-on: ubuntu-latest
permissions:
contents: write
packages: write

steps:
- name: Checkout source code
uses: actions/[email protected]
with:
fetch-depth: 0
fetch-tags: true

- name: Create GitHub tag
run: |
git config user.name "${{ github.actor }}"
git config user.email "${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com"
git tag -f ${{ inputs.github_tag }}
git push origin ${{ inputs.github_tag }} --force

- name: Set up JDK
uses: actions/[email protected]
with:
distribution: temurin
java-version: 21
cache: maven

- name: Build container image
run: |
chmod +x mvnw
echo "==================== spring-boot:build-image ===================="
./mvnw spring-boot:build-image \
-Dspring-boot.build-image.imageName=ten1010io/project-controller:${{ inputs.github_tag }} \
-DskipTests

- name: Login to Harbor Registry
uses: docker/login-action@v3
with:
registry: ${{ secrets.HARBOR_REGISTRY }}
username: ${{ secrets.HARBOR_USERNAME }}
password: ${{ secrets.HARBOR_PASSWORD }}

- name: Publish container image
run: |
HARBOR_IMAGE="${{ secrets.HARBOR_REGISTRY }}/project-controller/project-controller:${{ inputs.github_tag }}"

echo "==================== docker tag ===================="
docker tag ten1010io/project-controller:${{ inputs.github_tag }} ${HARBOR_IMAGE}

echo "==================== docker push ===================="
docker push ${HARBOR_IMAGE}

echo "Pushed: ${HARBOR_IMAGE}"

summary:
name: Summary
needs: build-and-push
runs-on: ubuntu-latest

steps:
- name: Summary
run: |
echo "### Build and Push Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| Tag | \`${{ inputs.github_tag }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Harbor Image | \`${{ secrets.HARBOR_REGISTRY }}/project-controller/project-controller:${{ inputs.github_tag }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Triggered by | @${{ github.actor }} |" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Image pushed to Harbor**" >> $GITHUB_STEP_SUMMARY
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,11 @@ build/

### VS Code ###
.vscode/
/.sdkmanrc
/CLAUDE.md
/k8s-dev
/.claude/worktrees/
/.claude/hooks/codex-review.sh
/.claude/skills/review/SKILL.md
/.claude/settings.json
harbor/.env
120 changes: 120 additions & 0 deletions DEPLOY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# Production Deploy Guide

## 사전 준비 (맥북)

```bash
# 이미지 빌드 & Harbor push
./image_push.sh 1.5.0
```

## 프로덕션 서버에서 실행

### 1. 기존 caBundle 값 확인

```bash
CA_BUNDLE=$(sudo kubectl get mutatingwebhookconfiguration project-controller.project.aipub.ten1010.io \
-o jsonpath='{.webhooks[0].clientConfig.caBundle}')
echo $CA_BUNDLE
```

### 2. 컨트롤러 이미지 업데이트

```bash
sudo kubectl -n project-controller set image deployment/project-controller \
project-controller=registry.ten1010.io:8443/project-controller/project-controller:1.5.0

# 롤아웃 확인
sudo kubectl -n project-controller rollout status deployment/project-controller --timeout=120s
```

### 3. v2 웹훅 apply

```bash
# caBundle 치환 후 apply
sed "s|<CA_BUNDLE>|${CA_BUNDLE}|g" \
./mutating-webhook-user-v2.yaml \
| sudo kubectl apply -f -
```

### 4. 확인

```bash
# 웹훅 등록 확인
sudo kubectl get mutatingwebhookconfiguration | grep user-v2

# 컨트롤러 로그
sudo kubectl -n project-controller logs deployment/project-controller -f --tail=50
```

### 5. 테스트

```bash
# 사용자 네임스페이스 확인 (시스템 네임스페이스 제외)
sudo kubectl get ns --no-headers | grep -v -E "kube-system|kube-public|kube-node-lease|kube-flannel|aipub|coaster|harbor|ingress-nginx|keycloak|linkerd|metallb|project-controller|aipub-promstack|trident|aipub-efk"

# 테스트할 네임스페이스에서 deployment 생성 (네임스페이스 변경해서 사용)
sudo kubectl -n taehyeong create deployment test-v2 --image=nginx

# v2 라벨 확인
sudo kubectl -n taehyeong get workspace label2 -o jsonpath='{.metadata.labels}' | jq .

# v2 owner annotation 확인
sudo kubectl -n taehyeong get workspace label2 -o jsonpath='{.metadata.annotations.aipub\.ten1010\.io/owner-reference-v2}' | jq .

# 실제 ownerReference 확인 (v1 기존 방식)
sudo kubectl -n taehyeong get workspace label2 -o jsonpath='{.metadata.ownerReferences}' | jq .

# 정리
sudo kubectl -n <NAMESPACE> delete deployment test-v2
```

### 6. 기존 라벨/ownerReference 확인

```bash
# 기존 라벨 확인 (v1)
sudo kubectl -n <NAMESPACE> get deployment test-v2 -o jsonpath='{.metadata.labels}' | jq .

# 기존 ownerReference 확인 (v1)
sudo kubectl -n <NAMESPACE> get deployment test-v2 -o jsonpath='{.metadata.ownerReferences}' | jq .

# v2 라벨 확인
sudo kubectl -n <NAMESPACE> get deployment test-v2 -o jsonpath='{.metadata.labels.aipub\.ten1010\.io/username-v2}'

# v2 owner annotation 확인
sudo kubectl -n <NAMESPACE> get deployment test-v2 -o jsonpath='{.metadata.annotations.aipub\.ten1010\.io/owner-reference-v2}' | jq .
```

### 7. 특정 라벨로 리소스 검색

```bash
# v2 username 라벨로 검색
sudo kubectl -n taehyeong get all -l aipub.ten1010.io/username-v2=taehyeong

# v2 userid 라벨로 검색
sudo kubectl -n taehyeong get all -l aipub.ten1010.io/userid-v2=test-user-001

# 기존 v1 username 라벨로 검색
sudo kubectl -n <NAMESPACE> get all -l aipub.ten1010.io/username=testuser

# 특정 네임스페이스 전체에서 v2 라벨 가진 리소스 검색
sudo kubectl -n <NAMESPACE> get deployments,pods,services,configmaps,secrets -l aipub.ten1010.io/username-v2

# 모든 네임스페이스에서 검색
sudo kubectl get deployments --all-namespaces -l aipub.ten1010.io/username-v2=testuser
```

## 한줄 배포 명령어

```bash
CA_BUNDLE=$(sudo kubectl get mutatingwebhookconfiguration project-controller.project.aipub.ten1010.io -o jsonpath='{.webhooks[0].clientConfig.caBundle}') && sed "s|<CA_BUNDLE>|${CA_BUNDLE}|g" kubernetes/controller/project-controller/templates/mutating-webhook-user-v2.yaml | sudo kubectl apply -f -
```

## 롤백

```bash
# v2 웹훅만 제거 (기존 웹훅에 영향 없음)
sudo kubectl delete mutatingwebhookconfiguration userrelationship-v2.project-controller.project.aipub.ten1010.io

# 컨트롤러 이미지 이전 버전으로 복구
sudo kubectl -n project-controller rollout undo deployment/project-controller
```
3 changes: 3 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FROM eclipse-temurin:21-jre
COPY target/*.jar /app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]
45 changes: 45 additions & 0 deletions harbor/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Harbor Image Push Setup (macOS)

## 1. harbor/.env 설정

```
HARBOR_REGISTRY=<url>
HARBOR_USERNAME=<username>
HARBOR_PASSWORD=<password>
```

## 2. /etc/hosts 추가

Harbor가 토큰 인증 시 내부 주소(`vnode2.pnode1.idc1.ten1010.io`)로 리다이렉트하므로, 외부 IP로 매핑 필요:

```bash
sudo sh -c 'echo "101.202.0.27 vnode2.pnode1.idc1.ten1010.io" >> /etc/hosts'
```

## 3. Docker Desktop insecure registry 설정

Harbor 인증서가 self-signed이므로 Docker Desktop에서 insecure registry로 등록:

Docker Desktop → Settings → Docker Engine:

```json
{
"insecure-registries": [
"vnode2.pnode1.idc1.ten1010.io:8443",
"external.vnode2.pnode1.idc1.ten1010.io:8443"
]
}
```

Apply & Restart.

## 4. 이미지 빌드 & Push

```bash
./image_push.sh <tag>

# 예시
./image_push.sh 1.5.0
```

이미지 경로: `vnode2.pnode1.idc1.ten1010.io:8443/project-controller/project-controller:<tag>`
51 changes: 51 additions & 0 deletions image_push.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!/usr/bin/env bash
set -euo pipefail

if [ -z "${1:-}" ]; then
echo "Usage: ./image_push.sh <tag>"
echo "Example: ./image_push.sh 1.5.0"
exit 1
fi

TAG="$1"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"

# Load harbor config
if [ -f "$SCRIPT_DIR/harbor/.env" ]; then
set -a
source "$SCRIPT_DIR/harbor/.env"
set +a
fi

if [ -z "${HARBOR_REGISTRY:-}" ]; then
echo "HARBOR_REGISTRY not set in harbor/.env"
exit 1
fi
REGISTRY="${HARBOR_REGISTRY}"
IMAGE="${REGISTRY}/project-controller/project-controller:${TAG}"

echo "=== Login to Harbor ==="
if [ -n "${HARBOR_USERNAME:-}" ] && [ -n "${HARBOR_PASSWORD:-}" ]; then
echo "${HARBOR_PASSWORD}" | docker login "${REGISTRY}" -u "${HARBOR_USERNAME}" --password-stdin
else
echo "HARBOR_USERNAME or HARBOR_PASSWORD not set in harbor/.env"
exit 1
fi

echo ""
echo "=== Build jar ==="
./mvnw -DskipTests clean package -q

echo ""
echo "=== Build image (linux/amd64) ==="
docker buildx build --platform linux/amd64 \
-t "${IMAGE}" \
-f Dockerfile . \
--load

echo ""
echo "=== Push image ==="
docker push "${IMAGE}"

echo ""
echo "Done: ${IMAGE}"
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
name: userrelationship-v2.project-controller.project.aipub.ten1010.io
webhooks:
- name: userrelationship-v2.project-controller.project.aipub.ten1010.io
admissionReviewVersions: [ "v1" ]
clientConfig:
caBundle: <CA_BUNDLE>
service:
namespace: project-controller
name: project-controller
path: /api/v1/userrelationship/mutate
port: 8080
failurePolicy: Ignore
matchPolicy: Equivalent
namespaceSelector:
matchExpressions:
- key: kubernetes.io/metadata.name
operator: NotIn
values:
- kube-system
- kube-public
- kube-node-lease
- kube-flannel
- aipub
- coaster
- harbor
- ingress-nginx
- keycloak
- linkerd
- metallb
- project-controller
- aipub-promstack
- trident
- aipub-efk
objectSelector: { }
reinvocationPolicy: IfNeeded
rules:
- apiGroups: [ "" ]
apiVersions: [ "*" ]
operations: [ "CREATE" ]
resources: [ "pods", "replicationcontrollers", "services", "configmaps", "secrets", "persistentvolumeclaims" ]
scope: "Namespaced"
- apiGroups: [ "networking.k8s.io" ]
apiVersions: [ "*" ]
operations: [ "CREATE" ]
resources: [ "ingresses" ]
scope: "Namespaced"
- apiGroups: [ "batch" ]
apiVersions: [ "*" ]
operations: [ "CREATE" ]
resources: [ "*" ]
scope: "Namespaced"
- apiGroups: [ "apps" ]
apiVersions: [ "*" ]
operations: [ "CREATE" ]
resources: [ "*" ]
scope: "Namespaced"
- apiGroups: [ "aipub.ten1010.io" ]
apiVersions: [ "*" ]
operations: [ "CREATE" ]
resources: [ "*" ]
scope: "Namespaced"
sideEffects: None
timeoutSeconds: 10
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@ public class AipubProperties {
@Nullable
private String password;
private List<String> reservedNamespace = new ArrayList<>();
private List<String> addOwnerExceptGvkList = new ArrayList<>();

}
Loading