๐ค 09. CICD ๊ณ ๊ธ
1. ๋ฆฌํฌ์งํ ๋ฆฌ ํฌํฌ ํ๊ธฐ
# ํ์ฌ ๊ณ์ ๋ฐ ์ฐ๊ฒฐ ์ํ ํ์ธ
gh auth status
# ๊ณ์ ์ฐ๊ฒฐ
gh auth login
# ์ ํ๋ฆฌ์ผ์ด์
๋ฆฌํฌ์งํ ๋ฆฌ Fork with Clone
gh repo fork https://github.com/dangtong76/istory-web-k8s.git --clone=false
git clone https://github.com/<your-id>/istory-web-k8s.git istory-web
# ํ๋ซํผ ๋ฆฌํฌ์งํ ๋ฆฌ Fork with Clone
gh repo fork https://github.com/dangtong76/istory-platform-k8s.git --clone=false
git clone https://github.com/<your-id>/istory-platform-k8s.git istory-platform
### - ๋๋ ํ ๋ฆฌ ์์ฑํ๊ธฐ
labs/istory-platform ์์ ์ํ
```bash
mkdir -p aws-eks/base/istory-app
mkdir -p aws-eks/base/istory-db
mkdir -p aws-eks/base/istory-tools
mkdir -p aws-eks/overlay/aws-dev
mkdir -p aws-eks/overlay/aws-prod
mkdir -p aws-eks/overlay/local-dev
- ํ์ผ ์์ฑํ๊ธฐ
labs/istory-platform ์์ ์ํ
touch aws-eks/base/istory-app/istory-app-config.yml
touch aws-eks/base/istory-app/istory-app-deploy.yml
touch aws-eks/base/istory-app/istory-app-lb.yml
touch aws-eks/base/istory-app/kustomization.yml
touch aws-eks/base/istory-db/istory-db-pod.yml
touch aws-eks/base/istory-db/istory-db-lb.yml
touch aws-eks/base/istory-db/istory-db-pvc.yml
touch aws-eks/base/istory-db/istory-db-sc.yml
touch aws-eks/base/istory-db-init-config.yml
touch aws-eks/base/istory-db/kustomization.yml
touch aws-eks/base/istory-tools/busybox.yml
touch aws-eks/base/istory-tools/kustomization.yml
touch aws-eks/overlay/aws-dev/kustomization.yml
touch aws-eks/overlay/aws-dev/patch-deploy.yml
touch aws-eks/overlay/aws-dev/patch-lb-annotations.yml
touch aws-eks/overlay/aws-dev/.env.secret
- ๊ฐ๋ฐ ๋ค์์คํ์ด์ค ์์ฑ
kubectl create ns istory-dev
3. ๋์ปค ํ์ผ ๋น๋ ๋ฐ ์ ๋ก๋
- ๋์ปค ํ์ผ์์ฑ
ํ์ผ์์น : xinfra/docker/Dockerfile
FROM eclipse-temurin:21-jdk-alpine
VOLUME /tmp
RUN addgroup -S istory && adduser -S istory -G istory
USER istory
WORKDIR /home/istory
COPY *.jar /home/istory/istory.jar
ENTRYPOINT ["java","-jar","/home/istory/istory.jar"]
- ์๋ฐ ๋น๋ (Gradle)
gradlew ๊ฐ ์๋ ๋๋ ํ ๋ฆฌ ์์น์์ ์คํ ํฉ๋๋ค.
./gradlew build -x test
๋น๋ ํ์๋ istory-web-k8s/build/libs/springbootdeveloper-0.0.1-SNAPSHOT.jar ์ด ์์ฑ ๋ฉ๋๋ค.
์ด ํ์ผ์ ๋์ปค build ๋ฅผ ์ํด์ istory-web-k8s/xinfra/docker ๋๋ ํ ๋ฆฌ ์์ ๋ณต์ฌ ํฉ๋๋ค. ํ์ง๋ง ๊ตณ์ด ๋ณต์ฌํ์ง ์์๋ build.gradle ํ์ผ์๋ ๋น๋๊ฐ ์ฑ๊ณตํ ๊ฒฝ์ฐ ์๋ ๋ณต์ฌํ๋ ๊ตฌ๋ฌธ์ด ๋ค์ด ์์ต๋๋ค.
task copyJarToBin(type: Copy) {
from "build/libs/springbootdeveloper-0.0.1-SNAPSHOT.jar"
into "xinfra/docker/"
}
๋ง์ฝ ํ์ผ์ด ์๋ค๋ฉด ์๋์ ๊ฐ์ด ์๋์ผ๋ก ๋ณต์ฌ ํ์ธ์!
cp build/libs/springbootdeveloper-0.0.1-SNAPSHOT.jar xinfra/docker/
- ๋์ปค ํ์ผ ๋น๋ ๋ฐ ์ ๋ก๋
# xinfra/docker ์์ ์ํ
# ์ปจํ
์ด๋ ์ด๋ฏธ์ง ๋น๋
docker build -t <your-docker-hub-id>/istory:1 .
# ๋ฉํฐ ํ๋ซํผ ๋น๋
docker buildx build --platform linux/amd64,linux/arm64 -t <your-dockerhub-id>/<image-name> --push .
# latest ํ๊ทธ ๋ง๋ค๊ธฐ
docker tag <your-docker-hub-id>/istory:1 <your-docker-hub-id>/istory:latest
# Docker hub ๋ฆฌํฌ์งํ ๋ฆฌ ๋ก๊ทธ์ธ
docker login --username <your-docker-hub-id>
# ์
๋ก๋
docker push <your-docker-hub-id>/istory:1
docker push <your-docker-hub-id>/istory:latest
4. istory-app base ์์ฑ
- istory-app-config.yml
์์น : xinfra/aws-eks/base/istory-app
apiVersion: v1
kind: ConfigMap
metadata:
name: istory-app-config
data:
spring.datasource.url: 'jdbc:mysql://istory-db-lb:3306/istory'
spring.datasource.driver-class-name: 'com.mysql.cj.jdbc.Driver'
spring.jpa.database-platform: 'org.hibernate.dialect.MySQLDialect'
spring.jpa.hibernate.ddl-auto: 'update'
spring.jpa.show-sql: 'true'
spring.application.name: 'USER-SERVICE'
- istory-app-deploy.yml
์์น : xinfra/aws-eks/base/istory-app
์ปจํ
์ด๋ ์ด๋ฏธ์ง ์ด๋ฆ์ ๋ฐ๋์ ์์ ์ Docker Hub ๊ณ์ ์ผ๋ก ๋ณ๊ฒฝ ํด์ผํจ.
apiVersion: apps/v1
kind: Deployment
metadata:
name: istory-app-deploy
labels:
app: istory-app
spec:
selector:
matchLabels:
app: istory-app
replicas: 3
template:
metadata:
labels:
app: istory-app
spec:
initContainers:
- name: check-mysql-ready
image: mysql:8.0
env:
- name: MYSQL_USER
valueFrom:
secretKeyRef:
name: istory-db-secret
key: MYSQL_USER
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
name: istory-db-secret
key: MYSQL_PASSWORD
command: ['sh',
'-c',
'until mysqladmin ping -u ${MYSQL_USER} -p${MYSQL_PASSWORD} -h istory-db-lb; do echo waiting for database; sleep 2; done;']
containers:
- name: istory
image: <your-docker-hub-account-id>/istory:latest # ๋ณ๊ฒฝํ์
envFrom:
- configMapRef:
name: istory-app-config
env:
- name: spring.datasource.password
valueFrom:
secretKeyRef:
name: istory-db-secret
key: MYSQL_PASSWORD
- name: spring.datasource.username
valueFrom:
secretKeyRef:
name: istory-db-secret
key: MYSQL_USER
readinessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 60
timeoutSeconds: 3
successThreshold: 2
failureThreshold: 3
periodSeconds: 20
ports:
- containerPort: 3306
name: istory
volumes:
- name: application-config
configMap:
name: istory-app-config
restartPolicy: Always
- istory-app-lb.yml
์์น : xinfra/aws-eks/base/istory-app
apiVersion: v1
kind: Service
metadata:
name: istory-app-lb
spec:
type: LoadBalancer
selector:
app: istory-app
sessionAffinity: ClientIP
sessionAffinityConfig:
clientIP:
timeoutSeconds: 800
ports:
- name: istory-app-lb
protocol: TCP
port: 80
targetPort: 8080
- kustomization.yml
์์น : xinfra/aws-eks/base/istory-app
resources:
- istory-app-config.yml
- istory-app-deploy.yml
- istory-app-lb.yml
5. istory-db base ์์ฑ
- istory-db-lb.yml
์์น : xinfra/aws-eks/base/istory-db/
apiVersion: v1
kind: Service
metadata:
name: istory-db-lb
spec:
selector:
app: mysql
ports:
- name: mysql-db-lb
protocol: TCP
port: 3306
- istory-db-pod.yml
์์น : xinfra/aws-eks/base/istory-db/
apiVersion: v1
kind: Pod
metadata:
name: istory-db
labels:
app: mysql
spec:
containers:
- image: mysql:5.6
name: mysql
envFrom:
- secretRef:
name: istory-db-secret
ports:
- containerPort: 3306
name: mysql
volumeMounts:
- name: mysql-persistent-storage
mountPath: /var/lib/mysql
- name: initdb
mountPath: /docker-entrypoint-initdb.d # mysql ์ปจํ
์ด๋๊ฐ ์คํ๋๋ฉด ์ต์ด๋ก ์๋ ์คํ๋๋ ๋๋ ํ ๋ฆฌ
volumes:
- name: mysql-persistent-storage
persistentVolumeClaim:
claimName: mysql-pv-claim
- name: initdb
configMap:
name: mysql-initdb-config
- istory-db-pvc.yml
์์น : xinfra/aws-eks/base/istory-db
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pv-claim
labels:
app: mysql
spec:
storageClassName: gp2-persistent
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
- istory-db-sc.yml
์์น : xinfra/aws-eks/base/istory-db
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: gp2-persistent
provisioner: kubernetes.io/aws-ebs
reclaimPolicy: Retain
parameters:
type: gp2
fsType: ext4
volumeBindingMode: WaitForFirstConsumer
- istory-db-init-config.yml
์์น : xinfra/aws-eks/base/istory-db
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: gp2-persistent
provisioner: kubernetes.io/aws-ebs
reclaimPolicy: Retain
parameters:
type: gp2
fsType: ext4
volumeBindingMode: WaitForFirstConsumer
kustomization.yml
์์น : xinfra/aws-eks/base/istory-db
resources:
- istory-db-pod.yml
- istory-db-lb.yml
- istory-db-pvc.yml
- istory-db-sc.yml
- istory-db-init-config.yml
6. istroy-tools base ์์ฑ
- busybox.yml
์์น : xinfra/aws-eks/base/istory-tools
apiVersion: v1
kind: Pod
metadata:
name: ubuntu-pod
spec:
containers:
- name: ubuntu-container
image: ubuntu
command: ["sleep", "infinity"]
- kustomization.yml
์์น : xinfra/aws-eks/base/istory-tools
resources:
- busybox.yml
7. aws-dev overlay ์์ฑ
์์น : xinfra/aws-eks/overlay/aws-dev
- patch-deploy.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: istory-app-deploy
annotations:
istory.io/env: dev
istory.io/tier: backend-app
istory.io/infra: aws
spec:
replicas: 1
์์น : xinfra/aws-eks/overlay/aws-dev
- patch-lb-annotaions.yml
apiVersion: v1
kind: Service
metadata:
name: istory-lb
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: external
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
istory.io/infra: aws
istory.io/env: dev
istory.io/tier: app-lb
- kustomization.yml
์์น : xinfra/aws-eks/overlay/aws-dev
kustomiztion.yml ์์ newTag ํค์๋๋ฅผ ์ฌ์ฉํ๋ฉด, kustomize edit ๋ช ๋ น์ ํตํด์ ์ด๋ฏธ์ง๋ฅผ ๋ณ๊ฒฝ ํ ์ ์์ต๋๋ค.
kustomize ์์๋ secretGenerator, configMapGenerator ๋ฅผ ์ ๊ณต ํฉ๋๋ค. ์ ๋๋ ์ดํฐ๋ฅผ ์ฌ์ฉํ๋ฉด .env ํ์ผ์ ํตํด ์ ๋ณด๋ฅผ ๋ง๋ค๋ฉด kustomize๊ฐ ConfigMap๊ณผ Secret ๋ฅผ ์๋์ผ๋ก ๋ง๋ค์ด ์ค๋๋ค.
resources:
- ../../base/istory-app
- ../../base/istory-db
namespace: istory-dev
patches:
- path: patch-lb-annotations.yml
target:
kind: Service
name: istory-app-lb
- path: patch-deploy.yml
target:
kind: Deployment
name: istory-app-deploy
secretGenerator:
- name: istory-db-secret
envs:
- .env.secret
# ์๋ ์์์ผ๋ก .env.secret ํ์ผ์ ๋ง๋์ธ์
# MYSQL_USER=myuser
# MYSQL_PASSWORD=myuserpassword
# MYSQL_ROOT_PASSWORD=myrootpassword
images:
# base/istory-app/istory-app-deploy.yml ๋ด์ ์ด๋ฏธ์ง ์ด๋ฆ๊ณผ ๋์ผํด์ผ ๋ณ๊ฒฝ๋จ
- name: <your-docker-hub-account-id>/istory # ๋ณ๊ฒฝํ์
newTag: latest
generatorOptions:
disableNameSuffixHash: true
- .env.secret ํ์ผ ์์ฑ
# ์๋ ์์์ผ๋ก .env.secret ํ์ผ์ ๋ง๋์ธ์
MYSQL_USER=myuser
MYSQL_PASSWORD=myuserpassword
MYSQL_ROOT_PASSWORD=myrootpassword
MYSQL_DATABASE=dbname
- kustomize ์ค์น
- ์คํ ํ์ผ ๋ค์ด๋ก๋
curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash
- PATH ๊ฑธ๋ ค์๋ ๋๋ ํ ๋ฆฌ๋ก ์ด๋
mv kustomize /usr/sbin/
- ๋ช ๋ น์ด ์คํ ํด๋ณด๊ธฐ
kustomize
- kustomize ๋น๋ ํด๋ณด๊ธฐ
- ๋น๋๊ฐ ์๋ฌ ์์ด ์ ์์ ์ธ ์ถ๋ ฅ์ ์์ฑํ๋ฉด OK!
kustomize build overlay/aws-dev
- ๊ธฐํ ์ฐธ๊ณ ์ฌํญ
kustomize ๋ช ๋ น์ด๋ก ์ด๋ฏธ์ง๋ฅผ ๋ณ๊ฒฝ ํ ๋๋ ์๋์ ๊ฐ์ด ๋ช ๋ น์ด ์ฌ์ฉ
# ์ปค๋ฐ ํด์๋ฅผ ์ด์ฉํ ํ๊น
๋ฐฉ๋ฒ
kustomize edit set image istory=dagntong76/istory:${{ github.sha }}
# ์ํฌํ๋ก์ฐ ์ํ ํ์๋ฅผ ์ด์ฉํ ํ๊น
๋ฐฉ๋ฒ
kustomize edit set image istory=dagntong76/istory:${{ github.run_number }}
- patch-deploy.yml ์์ ์ฌ์ฉํ๊ธฐ๊ธฐ
์ปจํ ์ด๋ ์ด๋ฏธ์ง ๋ถ๋ถ์ ์์ ์ Docker Hub ๊ณ์ ์ ์ด๋ฏธ์ง๋ก ๋ณ๊ฒฝ
apiVersion: apps/v1
kind: Deployment
metadata:
name: istory-app-deploy
annotations:
istory.io/env: dev
istory.io/tier: backend-app
istory.io/infra: aws
spec:
replicas: 1
template:
spec:
containers:
- name: istory
image: <your-docker-hub-account-id>/istory:latest # ๋ณ๊ฒฝํ์
์ด ๊ฒฝ์ฐ๋ ์๋์ ๊ฐ์ด workflow ์์ SED ๋ฅผ ์ฌ์ฉ
sed -i "s|image: .*|image: dangtong76/istory:${{ github.sha }}|" overlay/aws-dev/patch-deploy.yml
8. ArgoCD ์๋ฒ ์ค์น
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
9. ๋ก๋๋ฐธ๋ฐ์ ์ถ๊ฐํ๊ธฐ
# Patch์ผ ๊ฒฝ์ฐ Annotation์ด ์์ด์ Classic LB๊ฐ ์์ฑ๋๊ธฐ ๋๋ฌธ์ ์ธ๋ถ ์ ์ ๊ฐ๋ฅ
# ์ผ๋ฐ์ ์ผ๋ก Patch๊ฐ ์๋๋ผ Create์ผ ๊ฒฝ์ฐ Network LB ์์ฑ๋๊ณ , ์ด๋๋ Annotation ์์ด์ผํจ.
kubectl patch svc argocd-server -n argocd -p '{"spec": {"type": "LoadBalancer"}}'
10. ArgoCD CLI ์ค์น
์น VSCODE IDE๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ์ด๋ฏธ ์ค์น ๋์ด ์์
- Linux
# latest Version
curl -sSL -o argocd-linux-amd64 https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
sudo install -m 555 argocd-linux-amd64 /usr/local/bin/argocd
rm argocd-linux-amd64
# stable Version
VERSION=$(curl -L -s https://raw.githubusercontent.com/argoproj/argo-cd/stable/VERSION)
curl -sSL -o argocd-linux-amd64 https://github.com/argoproj/argo-cd/releases/download/v$VERSION/argocd-linux-amd64
sudo install -m 555 argocd-linux-amd64 /usr/local/bin/argocd
rm argocd-linux-amd64
- Windows
$version = (Invoke-RestMethod https://api.github.com/repos/argoproj/argo-cd/releases/latest).tag_name
$url = "https://github.com/argoproj/argo-cd/releases/download/" + $version + "/argocd-windows-amd64.exe"
$output = "argocd.exe"
Invoke-WebRequest -Uri $url -OutFile $output
- Mac
brew install argocd
11. Argocd ์๋ฒ ์ค์
- Admin ํจ์ค์๋ ์์๋ด๊ธฐ
argocd admin initial-password -n argocd
- ์ ์ Endpoint ์์๋ด๊ธฐ
kubectl get svc argocd-server -n argocd
###### ์ถ๋ ฅ ์์ ######
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
argocd-server LoadBalancer 172.20.21.170 a7eb417c510ec4551933abc911356e6e-1770617535.ap-northeast-2.elb.amazonaws.com 80:31698/TCP,443:31412/TCP 103s
- Argocd CLI ๋ก๊ทธ์ธ
argocd login <EXTERNAL-IP>
###### ์ถ๋ ฅ ์์ ######
WARNING: server certificate had error: tls: failed to verify certificate: x509: certificate signed by unknown authority. Proceed insecurely (y/n)? y
Username: admin
Password:
'admin:login' logged in successfully
Context 'a7eb417c510ec4551933abc911356e6e-1770617535.ap-northeast-2.elb.amazonaws.com' updated
- ์๋ก์ด ํจ์ค์๋๋ก ๋ณ๊ฒฝ
argocd account update-password --current-password <ํ์ฌํจ์ค์๋> --new-password <์๋ก์ดํจ์ค์๋>
12. ๋ธ๋ผ์ฐ์ ๊ธฐ๋ฐ ์ค์
- ๋ฆฌํฌ์งํ ๋ฆฌ ์ค์
- ์น ๋ฉ๋ด :
SettingsโRepositoriesโCONNECT REPO์ค์ ํญ๋ชฉ ๊ฐ ์ค๋ช connection method VIA HTTPS Git ๋ฆฌํฌ์งํ ๋ฆฌ ์ ์ ๋ฐฉ์ Type git ๋ฆฌํฌ์งํ ๋ฆฌ ํ์ (git Name istory-dev ์ฐธ์กฐ ์ด๋ฆ Project Default Git ์ ๋ธ๋ ์น ์ด๋ฆ Repository URL https://github.com/<github-id>/istory-platform.gitPlatform ๋ฆฌํฌ์งํ ๋ฆฌ URL - ์ค์ ํ๋ฉด ์ฐธ์กฐ

- ์ ํ๋ฆฌ์ผ์ด์ ์ค์
- ์น ๋ฉ๋ด :
ApplicationโNEW APP
| ์ค์ ํญ๋ชฉ | ๊ฐ | ์ค๋ช |
|---|---|---|
| Application Name | istory-dev | - |
| Project Name | default | - |
| Repository URL | https://github.com/<github-id>/istory-platform.git | - |
| Path | overlay | aws-dev |
| Cluster URL | https://kubernetes.default.svc | - |
| Namespace | istory-dev | - |
- ์ค์ ํ๋ฉด ์ฐธ์กฐ


- ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ํฌ๋ฆฟ ์์ฑ ํ๊ธฐ (.env.secret ์์ฑํ์ง ์์์ ๊ฒฝ์ฐ)
๋ฐ์ดํฐ๋ฒ ์ด์ค ๊ณ์ ๋ฐ ํจ์ค์๋๋ Github Action Secret์ ์ด์ฉํด์ ๋์ ์ผ๋ก ์์ฑ ํ๊ธฐ ๋๋ฌธ์ ์ต์ด์ ์ฑํฌ ์์๋ ์ง์ K8s Secret ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด์ผ ํจ.
Workflow ์์๋ Github Action Secret ์ ์ฐธ์กฐํด์ ๊ณ์ ์
๋ฐ์ดํธ(kubectl apply …) ๊ฐ๋ฅํ๋๋ก ํด์ผ ํจ.
kubectl create secret generic istory-db-secret \
--namespace istory-dev \
--from-literal=MYSQL_USER=user \
--from-literal=MYSQL_PASSWORD=user12345 \
--from-literal=MYSQL_DATABASE=istory \
--from-literal=MYSQL_ROOT_PASSWORD=admin123
- Sync ํ๊ธฐ
- ์น ๋ฉ๋ด :
ApplicationโSYNCโSynchronize
13. ๋ช ๋ น์ด ๊ธฐ๋ฐ ์ค์ (์ถํ ์ ๋ฐ์ดํธ ์์ )
์ถํ ์ ๋ฐ์ดํธ ์์ …
14. ์ํฌํ๋ก์ฐ ํ๊ฒฝ ์ค์
- ์๋ฐ ์์ค ์์
Github Action Workflow๋ฅผ ํตํด ๋น๋๋ ์ ํ๋ฆฌ์ผ์ด์
์ ํ์ธํ๊ธฐ ์ํด ์์ค์ ๋ฒ์ ์ ์ถ๊ฐ ํฉ๋๋ค.
ํ์ผ ์์น : src/main/resources/templates/login.html
์๋น์ค๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด ๋ก๊ทธ์ธ์ ํด์ฃผ์ธ์! ๋ท ๋ถ๋ถ์ ๋ฐฐํฌ ๋ฒ์ ์ถ๊ฐ V4.0
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>๋ก๊ทธ์ธ</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css">
<style>
.gradient-custom {
background: linear-gradient(to right, rgba(106, 17, 203, 1), rgba(37, 117, 252, 1))
}
</style>
</head>
<body class="gradient-custom">
<section class="d-flex vh-100">
<div class="container-fluid row justify-content-center align-content-center">
<div class="card bg-dark" style="border-radius: 1rem;">
<div class="card-body p-5 text-center">
<h2 class="text-white">LOGIN</h2>
<p class="text-white-50 mt-2 mb-5">์๋น์ค๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด ๋ก๊ทธ์ธ์ ํด์ฃผ์ธ์! V4.0</p>
<div class = "mb-2">
<form action="/login" method="POST">
<input type="hidden" th:name="${_csrf?.parameterName}" th:value="${_csrf?.token}" />
<div class="mb-3">
<label class="form-label text-white">Email address</label>
<input type="email" class="form-control" name="username">
</div>
<div class="mb-3">
<label class="form-label text-white">Password</label>
<input type="password" class="form-control" name="password">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
<button type="button" class="btn btn-secondary mt-3" onclick="location.href='/signup'">ํ์๊ฐ์
</button>
</div>
</div>
</div>
</div>
</section>
</body>
</html>
- Github ํ ํฐ์์ฑ
์ํฌํ๋ก์ฐ ์์ ์ฌ์ฉํ ACCESS TOKEN ๋ค์ ๊ฐ ์ฌ์ดํธ์ ๋ฐฉ๋ฌธํด์ ์์ฑ ํฉ๋๋ค.
Github Token ์์ฑ
Github ์ฌ์ดํธ์์ ์๋์ ๊ฐ์ด ํ ํฐ์ ์์ฑ ํฉ๋๋ค.Profileโsettingsโ< > Developer settingsโ๐ Personal access tokensโFine-grained tokensโGenerate new token์์ธ ์ ๋ ฅ ํญ๋ชฉ
์ ๋ ฅ ํญ๋ชฉ ์ ๋ ฅ ๊ฐ Token name personal_access_token Repository access All repositories ์ ์ฒดํฌ Repository Permission Read and Write for Actions, Administration, Codepsaces, Contents, Metadata, Pull requests, Secrets, Variables, Workflows
- Docker Hub ํ ํฐ ์์ฑ
๋์ปค ํ๋ธ ์ฌ์ดํธ ์์ ์๋์ ๊ฐ์ด ACCESS TOKEN์ ์์ฑํฉ๋๋ค.Profile โ Account Setting โ ๐ Personal access tokens โ Generate new token
- ์์ธ ์
๋ ฅ ํญ๋ชฉ
์ ๋ ฅ ํญ๋ชฉ ์ ๋ ฅ ๊ฐ Access token description Github Workflow TOKEN Expiration date 30 days Access permissions Read & Write
- ์ํฌ ํ๋ก์ฐ ๋๋ ํ ๋ฆฌ ์์ฑ
istory-web-k8s ๋๋ ํ ๋ฆฌ์์ ์ํ
mkdir -p .github/workflows
- create-secret.sh ์์ฑ
ํ์ผ ์์น : .github/workflows/create-secret.sh
gh api -X PUT repos/<your-github-id>/istory-web-k8s/environments/k8s-dev --silent
gh secret set DATABASE_URL --env k8s-dev --body "jdbc:mysql:istory-db-lb:3306/istory"
gh secret set MYSQL_DATABASE --env k8s-dev --body "istory"
gh secret set MYSQL_USER --env k8s-dev --body "user"
gh secret set MYSQL_PASSWORD --env k8s-dev --body "user12345"
gh secret set MYSQL_ROOT_PASSWORD --env k8s-dev --body "admin123"
gh secret set DOCKER_USERNAME --env k8s-dev --body "<your-docker-hub-id>"
gh secret set DOCKER_TOKEN --env k8s-dev --body "<your-docker-hub-token>"
gh secret set GIT_ACCESS_TOKEN --env k8s-dev --body "<your-github-access-token>"
gh secret set GIT_USERNAME --env k8s-dev --body "<your-github-id>"
gh secret set GIT_PLATFORM_REPO_NAME --env k8s-dev --body "<your-githhub-repository-name>"
gh secret set K8S_CLUSTER_NAME --env k8s-dev --body "istory"
gh secret set K8S_NAMESPACE --env k8s-dev --body "istory-dev"
gh secret set AWS_ACCESS_KEY_ID --env k8s-dev --body "<your-aws-access-key-id>"
gh secret set AWS_SECRET_ACCESS_KEY --env k8s-dev --body "<your-aws-access-key>"
gh secret set AWS_REGION --env k8s-dev --body "<your-aws-region>"
- ํ์ผ ๊ถํ ๋ถ์ฌ ๋ฐ ์คํ
chmod +x .github/workflows/create-secret.sh
.github/workflows/create-secret.sh
15. Workflow ์์ฑ
- MySQL ์๋น์ค ๋ฐ ํ๊ฒฝ์ค์
ํ์ผ ์์น : .github/workflows/istory-aws-eks-dev.yml
#8
name: Istory Deploy to AWS EKS DEV
on:
push:
branches: ['main', 'test']
permissions:
contents: read
actions: read
packages: write
jobs:
build:
if: contains(github.event.head_commit.message, '[deploy-dev]') # ํน์ ํ๊ทธ์๋ง ์คํ
runs-on: ubuntu-latest
environment: k8s-dev # ํ๊ฒฝ ๋ณ์ ๋ฐ ์ํฌ๋ฆฟ ์ ์ฅ๊ณต๊ฐ
env:
DOCKER_IMAGE: ${{ secrets.DOCKER_USERNAME }}/istory
DOCKER_TAG: ${{ github.run_number }}
services:
mysql:
image: mysql:8.0
env:
# root ๊ณ์ ๋น๋ฐ๋ฒํธ
MYSQL_ROOT_PASSWORD: ${{ secrets.MYSQL_ROOT_PASSWORD }}
# ์ฌ์ฉ์ ๊ณ์
MYSQL_USER: ${{ secrets.MYSQL_USER }} # user
# ์ฌ์ฉ์ ๊ณ์ ๋น๋ฐ๋ฒํธ
MYSQL_PASSWORD: ${{ secrets.MYSQL_PASSWORD }}
# ์ฌ์ฉ์ ๊ณ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค
MYSQL_DATABASE: ${{ secrets.MYSQL_DATABASE }} # istory
ports:
- 3306:3306
options: >-
--health-cmd="mysqladmin ping"
--health-interval=10s
--health-timeout=5s
--health-retries=3
steps:
- name: AWS CLI ActionSet ์ค์
uses: aws-actions/configure-aws-credentials@v3
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}
- name: kubectl ์ค์น
run: |
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x kubectl
sudo mv kubectl /usr/local/bin/
- name: kubeconfig ์
๋ฐ์ดํธ
run: |
aws eks update-kubeconfig --region ${{ secrets.AWS_REGION }} --name ${{ secrets.K8S_CLUSTER_NAME }}
- name: kustomize ์ค์น
run: |
curl -s https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh | bash
sudo mv kustomize /usr/local/bin
- name: JDK 17 ์ค์น
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- ์ ํ๋ฆฌ์ผ์ด์ ๋น๋
ํ์ผ ์์น : .github/workflows/istory-aws-eks-dev.yml
- name: ์์ค์ฝ๋ ๋ค์ด๋ก๋
uses: actions/checkout@v4
with:
ref: ${{ github.ref }}
path: .
- name: ๊ฐ๋ฐ์ฉ application.yml ์์ฑ
run: |
cat > src/main/resources/application.yml << EOF
spring:
datasource:
# url: ${{ secrets.DATABASE_URL }} # ์dbc:mysql://localhost:3306/istory
url: jdbc:mysql://localhost:3306/istory
username: ${{ secrets.MYSQL_USER }}
password: ${{ secrets.MYSQL_PASSWORD }}
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
database-platform: org.hibernate.dialect.MySQL8Dialect
hibernate:
ddl-auto: update
show-sql: true
application:
name: USER-SERVICE
jwt:
issuer: user@gmail.com
secret_key: study-springboot
management:
endpoints:
web:
exposure:
include: health,info
endpoint:
health:
show-details: always
EOF
- name: Gradle ์ค์
uses: gradle/gradle-build-action@v2
- name: JAVA build with TEST
run: ./gradlew build
- name: Docker ๋๋ ํ ๋ฆฌ๋ก JAR ํ์ผ ๋ณต์ฌ
run: |
mkdir -p xinfra/docker/build/libs/
cp build/libs/*.jar xinfra/docker/
ls xinfra/docker/
- ์ปจํ ์ด๋ ๋น๋ ๋ฐ ์ ๋ก๋
ํ์ผ ์์น : .github/workflows/istory-aws-eks-dev.yml
- name: ์ปจํ
์ด๋ ์ด๋ฏธ์ง ๋น๋
run: |
docker build ./xinfra/docker -t ${{ secrets.DOCKER_USERNAME }}/istory:${{ env.DOCKER_TAG }} -f ./xinfra/docker/Dockerfile
docker tag ${{ secrets.DOCKER_USERNAME }}/istory:${{ env.DOCKER_TAG }} ${{ secrets.DOCKER_USERNAME }}/istory:latest
- name: Docker Hub ๋ก๊ทธ์ธ
uses: docker/login-action@v3.0.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
logout: true
- name: Docker Hub ์ด๋ฏธ์ง ์
๋ก๋
run: |
docker push ${{ secrets.DOCKER_USERNAME }}/istory:${{ env.DOCKER_TAG }}
docker push ${{ secrets.DOCKER_USERNAME }}/istory:latest
- 2.4 ์ด๋ฏธ์ง ํ๊ทธ ๋ณ๊ฒฝ
ํ์ผ ์์น : .github/workflows/istory-aws-eks-dev.yml
- name: ์๋น์ค ๋ฆฌํฌ์งํ ๋ฆฌ ์ฒดํฌ์์
uses: actions/checkout@v4
with:
repository: ${{ secrets.GIT_USERNAME }}/${{ secrets.GIT_PLATFORM_REPO_NAME }}
ref: ${{ github.ref }} # ๋ฐ๊พธ๊ธฐ
path: .
token: ${{ secrets.GIT_ACCESS_TOKEN }}
- name: ์ฟ ๋ฒ๋คํฐ์ค secret ์์ฑ (istory-db-secret)
run: |
kubectl create secret generic istory-db-secret \
--from-literal=MYSQL_USER=${{ secrets.MYSQL_USER }} \
--from-literal=MYSQL_PASSWORD=${{ secrets.MYSQL_PASSWORD }} \
--from-literal=DATABASE_URL=${{ secrets.DATABASE_URL }} \
--from-literal=MYSQL_ROOT_PASSWORD=${{ secrets.MYSQL_ROOT_PASSWORD }} \
--namespace=${{ secrets.K8S_NAMESPACE}} \
--dry-run=client -o yaml | kubectl apply -f -
- name: ์ด๋ฏธ์ง ํ๊ทธ ์
๋ฐ์ดํธ (kustomize)
run: |
cd overlay/aws-dev
kustomize edit set image ${{ secrets.DOCKER_USERNAME }}/istory=${{ secrets.DOCKER_USERNAME }}/istory:${{ env.DOCKER_TAG }}
- name: ์๋น์ค ๋ฆฌํฌ์งํ ๋ฆฌ ์ต์ข
์
๋ฐ์ดํธ
run: |
git config --global user.name 'github-actions[bot]'
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
git commit -am "Update image tag to ${{ env.DOCKER_TAG }}"
git push origin ${{ github.ref_name }}
- ์ ์ฒดํ์ผ
#8
name: Istory Deploy to AWS EKS DEV
on:
push:
branches: [ 'main', 'test' ]
permissions:
contents: read
actions: read
packages: write
jobs:
build:
if: contains(github.event.head_commit.message, '[deploy-dev]') # ํน์ ํ๊ทธ์๋ง ์คํ
runs-on: ubuntu-latest
environment: k8s-dev # ํ๊ฒฝ ๋ณ์ ๋ฐ ์ํฌ๋ฆฟ ์ ์ฅ๊ณต๊ฐ
env:
DOCKER_IMAGE: ${{ secrets.DOCKER_USERNAME }}/istory
DOCKER_TAG: ${{ github.run_number }}
services:
mysql:
image: mysql:8.0
env:
# root ๊ณ์ ๋น๋ฐ๋ฒํธ
MYSQL_ROOT_PASSWORD: ${{ secrets.MYSQL_ROOT_PASSWORD }}
# ์ฌ์ฉ์ ๊ณ์
MYSQL_USER: ${{ secrets.MYSQL_USER }} # user
# ์ฌ์ฉ์ ๊ณ์ ๋น๋ฐ๋ฒํธ
MYSQL_PASSWORD: ${{ secrets.MYSQL_PASSWORD }}
# ์ฌ์ฉ์ ๊ณ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค
MYSQL_DATABASE: ${{ secrets.MYSQL_DATABASE }} # istory
ports:
- 3306:3306
options: >-
--health-cmd="mysqladmin ping"
--health-interval=10s
--health-timeout=5s
--health-retries=3
steps:
- name: AWS CLI ActionSet ์ค์
uses: aws-actions/configure-aws-credentials@v3
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}
- name: kubectl ์ค์น
run: |
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x kubectl
sudo mv kubectl /usr/local/bin/
- name: kubeconfig ์
๋ฐ์ดํธ
run: |
aws eks update-kubeconfig --region ${{ secrets.AWS_REGION }} --name ${{ secrets.K8S_CLUSTER_NAME }}
- name: kustomize ์ค์น
run: |
curl -s https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh | bash
sudo mv kustomize /usr/local/bin
- name: JDK 17 ์ค์น
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: ์์ค์ฝ๋ ๋ค์ด๋ก๋
uses: actions/checkout@v4
with:
ref: ${{ github.ref }}
path: .
- name: ๊ฐ๋ฐ์ฉ application.yml ์์ฑ
run: |
cat > src/main/resources/application.yml << EOF
spring:
datasource:
# url: ${{ secrets.DATABASE_URL }} # ์dbc:mysql://localhost:3306/istory
url: jdbc:mysql://localhost:3306/istory
username: ${{ secrets.MYSQL_USER }}
password: ${{ secrets.MYSQL_PASSWORD }}
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
database-platform: org.hibernate.dialect.MySQL8Dialect
hibernate:
ddl-auto: update
show-sql: true
application:
name: USER-SERVICE
jwt:
issuer: user@gmail.com
secret_key: study-springboot
management:
endpoints:
web:
exposure:
include: health,info
endpoint:
health:
show-details: always
EOF
- name: Gradle ์ค์
uses: gradle/gradle-build-action@v2
- name: JAVA build with TEST
run: ./gradlew build -x test
- name: Docker ๋๋ ํ ๋ฆฌ๋ก JAR ํ์ผ ๋ณต์ฌ
run: |
mkdir -p xinfra/docker/build/libs/
cp build/libs/*.jar xinfra/docker/
ls xinfra/docker/
- name: ์ปจํ
์ด๋ ์ด๋ฏธ์ง ๋น๋
run: |
docker build ./xinfra/docker -t ${{ secrets.DOCKER_USERNAME }}/istory:${{ env.DOCKER_TAG }} -f ./xinfra/docker/Dockerfile
docker tag ${{ secrets.DOCKER_USERNAME }}/istory:${{ env.DOCKER_TAG }} ${{ secrets.DOCKER_USERNAME }}/istory:latest
- name: Docker Hub ๋ก๊ทธ์ธ
uses: docker/login-action@v3.0.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
logout: true
- name: Docker Hub ์ด๋ฏธ์ง ์
๋ก๋
run: |
docker push ${{ secrets.DOCKER_USERNAME }}/istory:${{ env.DOCKER_TAG }}
docker push ${{ secrets.DOCKER_USERNAME }}/istory:latest
- name: ์๋น์ค ๋ฆฌํฌ์งํ ๋ฆฌ ์ฒดํฌ์์
uses: actions/checkout@v4
with:
repository: ${{ secrets.GIT_USERNAME }}/${{ secrets.GIT_PLATFORM_REPO_NAME }}
ref: ${{ github.ref }} # istory-web-k8s, istory-platform ์ ๋์ผํ ๋ธ๋ ์น ๋ผ๋ ๊ฐ์
path: .
token: ${{ secrets.GIT_ACCESS_TOKEN }}
- name: ์ฟ ๋ฒ๋คํฐ์ค secret ์์ฑ (istory-db-secret)
run: |
kubectl create secret generic istory-db-secret \
--from-literal=MYSQL_USER=${{ secrets.MYSQL_USER }} \
--from-literal=MYSQL_PASSWORD=${{ secrets.MYSQL_PASSWORD }} \
--from-literal=DATABASE_URL=${{ secrets.DATABASE_URL }} \
--from-literal=MYSQL_ROOT_PASSWORD=${{ secrets.MYSQL_ROOT_PASSWORD }} \
--namespace=${{ secrets.K8S_NAMESPACE}} \
--dry-run=client -o yaml | kubectl apply -f -
- name: ์ด๋ฏธ์ง ํ๊ทธ ์
๋ฐ์ดํธ (kustomize)
run: |
ls
cd awsk-eks/overlay/aws-dev
kustomize edit set image ${{ secrets.DOCKER_USERNAME }}/istory=${{ secrets.DOCKER_USERNAME }}/istory:${{ env.DOCKER_TAG }}
- name: ์๋น์ค ๋ฆฌํฌ์งํ ๋ฆฌ ์ต์ข
์
๋ฐ์ดํธ
run: |
git config --global user.name 'github-actions[bot]'
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
git commit -am "Update image tag to ${{ env.DOCKER_TAG }}"
git push origin ${{ github.ref_name }}
16. ArgoCD ํ์ธ ๋ฐ ์น์ฌ์ดํธ ์ ์
์ ํ๋ฆฌ์ผ์ด์ ์์ ํ๊ฒ์ ์ปค๋ฐํ๊ณ ๋ฆฌํฌ์งํ ๋ฆฌ์ ์ ๋ก๋ํฉ๋๋ค.
- Commit ๋ฐ Push
git add .
git commit -am "[aws-dev deploy]add workflow"
git push origin main
- ArgoCD URL ์กฐํ ๋ฐ ์ ์
URL ์ ์ํด์ Commit Hash ๊ฐ์ ๋น๊ตํด์ ๋ฌ๋ผ ์ง๋์ง ํ์ธ ํ Sync ๋ฒํผ์ ๋๋ฌ ๋๊ธฐํ ํฉ๋๋ค.
kubectl get svc argocd-server -n argocd
- Istory URL ์กฐํ ๋ฐ ์ ์
Istory ์๋น์ค์ ์ ์ํด์ ๋ก๊ทธ์ธ ํ๋ฉด์ ๋ฒ์ ์ด ๋ง๋์ง ํ์ธ ํฉ๋๋ค.
kubectl get svc -n istory-dev
http://<site-url> ๋ก ์ ์
[์ฐ์ต๋ฌธ์ ] 9-1. aws-prod overlay ์์ฑ
- istory-prod ๋ผ๋ ๋ณ๋์ ๋ค์์คํ์ด์ค๋ฅผ ๋ง๋ญ๋๋ค.
- ๋ฐ์ดํฐ ๋ฒ ์ด์ค๋ AWS RDS๋ฅผ ์ฌ์ฉ ํ๋๋ก ํ
๋ผํผ ์ฝ๋๋ฅผ ์ถ๊ฐ ํฉ๋๋ค.
- overlay/aws-prod ๋๋ ํ ๋ฆฌ๋ฅผ kustomzie ์ ์ถ๊ฐ ํฉ๋๋ค.
- .env.secret ํ์ผ ์์ฑํฉ๋๋ค.
- kustomiztion.yml ์ DB ๋ถ๋ถ์ ์ญ์ ํฉ๋๋ค.(RDS ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์)
- patch-app-config.yml ํ์ด์ ๋ง๋ค์ด์ RDS URL ๋ก ๋ณ๊ฒฝ ํฉ๋๋ค.
- patch-deploy.yml ํ์ผ์ ๋ง๋ค์ด์ annotation(istory.io/env: prod) ๊ณผ replicas ๊ฐ์ ๋ณ๊ฒฝ ํฉ๋๋ค.
- patch-lb-annotations.yml ์ annotation(istory.io/env: prod)๋ก ๋ณ๊ฒฝ
- ๋ณ๊ฒฝ๋ ์ฝ๋๋ฅผ istory-platform-k8s ์ commit ํ push ํฉ๋๋ค.
- ArgoCD ์์ Repository ์์ฑ ๋ฐ Application ์ค์ ํฉ๋๋ค.
[์ฐ์ต๋ฌธ์ ] 9-2. ConfigMapGenerator ์ฌ์ฉ
ํ์ฌ istory-app-config.yml ํ์ผ์ ConfigMapGenerator ๋ฅผ ์ฌ์ฉํ๋๋ก ๋ณ๊ฒฝํ์ธ์
์ฐธ์กฐ ์ฌ์ดํธ : https://env.simplestep.ca/