feat: Kubernetes 자동 배포 파이프라인 구축
Build & Deploy / build-and-deploy (push) Failing after 1m56s

- Dockerfile: Expo web export → nginx multi-stage 빌드
- nginx.conf: SPA fallback, gzip, health endpoint
- K8s manifests: namespace, deployment (2 replicas), service, ingress
- Traefik IngressRoute (선택적) 포함
- Gitea Actions workflow: push 시 빌드→Gitea Registry push→rollout restart
- DEPLOY.md: 초기 설정 가이드 (kubeconfig, secrets, DNS)

Domain: insurance.junggomoa.com

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
chpark
2026-04-22 23:59:51 +09:00
parent 0a111c172f
commit 035eb0259f
10 changed files with 506 additions and 0 deletions
+61
View File
@@ -0,0 +1,61 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: insurance-web
namespace: insurance
labels:
app.kubernetes.io/name: insurance-web
app.kubernetes.io/component: frontend
spec:
replicas: 2
revisionHistoryLimit: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app.kubernetes.io/name: insurance-web
template:
metadata:
labels:
app.kubernetes.io/name: insurance-web
annotations:
kubectl.kubernetes.io/restartedAt: "placeholder-will-be-patched-by-ci"
spec:
imagePullSecrets:
- name: gitea-registry
containers:
- name: web
image: git.junggomoa.com/chpark/insurance:latest
imagePullPolicy: Always
ports:
- name: http
containerPort: 80
protocol: TCP
readinessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 3
periodSeconds: 5
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 15
periodSeconds: 20
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 300m
memory: 256Mi
securityContext:
allowPrivilegeEscalation: false
runAsNonRoot: false
capabilities:
drop: ["ALL"]
add: ["CHOWN", "SETGID", "SETUID", "NET_BIND_SERVICE"]
+25
View File
@@ -0,0 +1,25 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: insurance-web
namespace: insurance
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls: "true"
spec:
ingressClassName: traefik
tls:
- hosts:
- insurance.junggomoa.com
secretName: insurance-tls
rules:
- host: insurance.junggomoa.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: insurance-web
port:
number: 80
+43
View File
@@ -0,0 +1,43 @@
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: insurance-web
namespace: insurance
spec:
entryPoints:
- websecure
routes:
- match: Host(`insurance.junggomoa.com`)
kind: Rule
services:
- name: insurance-web
port: 80
tls:
secretName: insurance-tls
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: insurance-web-http
namespace: insurance
spec:
entryPoints:
- web
routes:
- match: Host(`insurance.junggomoa.com`)
kind: Rule
middlewares:
- name: insurance-redirect-https
services:
- name: insurance-web
port: 80
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: insurance-redirect-https
namespace: insurance
spec:
redirectScheme:
scheme: https
permanent: true
+7
View File
@@ -0,0 +1,7 @@
apiVersion: v1
kind: Namespace
metadata:
name: insurance
labels:
app.kubernetes.io/name: insurance
app.kubernetes.io/managed-by: gitea-actions
+16
View File
@@ -0,0 +1,16 @@
apiVersion: v1
kind: Service
metadata:
name: insurance-web
namespace: insurance
labels:
app.kubernetes.io/name: insurance-web
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: insurance-web
ports:
- name: http
port: 80
targetPort: http
protocol: TCP
+44
View File
@@ -0,0 +1,44 @@
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types
text/plain
text/css
text/javascript
text/xml
application/json
application/javascript
application/xml+rss
application/atom+xml
image/svg+xml;
location / {
try_files $uri $uri/ /index.html;
}
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
location = /index.html {
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
location = /health {
access_log off;
return 200 "ok\n";
add_header Content-Type text/plain;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}