
EKS 내용들을 적용해볼 수 있는 워크샵을 진행해보면서 기억하면 좋을 것들을 정리를 해보려고 한다!
EKS setup 확인
kubectl get nodes

Helm
helm은 k8s의 패키지 관리자라고 할 수 있다.
helm chart : 특정 디렉토리 구조로 구성된 yaml 템플릿 파일로 구성된 패키지
example-chart/
├── Chart.yaml
├── templates
│ ├── deployment.yaml
│ ├── rbac.yaml
│ └── service.yaml
└── values.yaml
helm CLI 설치
curl -sSL https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash
helm 차트 나열
helm list
helm 차트 배포
helm install <릴리스이름> <차트경로 또는 차트이름>

리소스 검색
kubectl get <리소스> -n <네임스페이스 이름>
RBAC
RBAC은 개별 사용자의 role에 따라 접근을 규제하는 방법이다.
사용자 생성 및 액세스 키 발급
aws iam create-user --user-name rbac-user
aws iam create-access-key --user-name rbac-user
IAM 사용자 클러스터에 연결
eksctl create iamidentitymapping \
--cluster <EKS_CLUSTER_NAME> \
--arn arn:aws:iam::<AWS_ACCOUNT_ID>:user/<IAM_USER_NAME> \
--username <K8S_USERNAME>
EKS 클러스터에서 해당 사용자를 쿠버네티스 사용자로 인식하게 해줌
여기까지 했으면, 사용자가 configmap에 등록되어, 쿠버네티스가 사용자가 접근할 수 있도록 허락하는 과정까지 온 것!
하지만, 접근만 할 수 있지 들어와서 할 수 있는 작업은 하나도 없는 상태이므로,
1. role을 정의하는 yaml파일을 만들고
2. rolebinding을 정의하는 yaml파일을 만들고
(이 과정에서 사용자에게 role을 붙여주게 된다.)
3. apply로 파일 적용
kubectl apply -f role.yaml
kubectl apply -f role-binding.yaml
권한 + 권한을 누가 가질 지 설정 완료
EKS 클러스터에 대한 액세스 권한을 부여하려면 kube-system 네임스페이스에 있는 aws-auth라는 클러스터 내 ConfigMap을 활용하여 AWS ID를 Kubernetes 그룹에 명시적으로 매핑해야한다.
Access management
EKS Cluster Access Management
IAM으로 직접 EKS 클러스터에 대한 접근 권한을 제어할 수 있는 공식 기능
예전처럼 aws-auth ConfigMap을 손으로 수정하지 않아도 된다!

관리 콘솔에서 옵션을 확인해보면, 현재 상태는 기존의 aws-auth configmap을 수정하는 방식과 새로운 EKS access entry API를 모두 사용할 수 있는 상태이다.
특정 클러스터 내의 접근 리스트 나열
aws eks list-access-entries --cluster-name <클러스터 이름>
policy연결
aws eks associate-access-policy \
--cluster-name <EKS_CLUSTER_NAME> \
--principal-arn arn:aws:iam::<AWS_ACCOUNT_ID>:user/<IAM_USER_NAME> \
--policy-arn arn:aws:eks::aws:cluster-access-policy/<POLICY_NAME> \
--access-scope type=<SCOPE_TYPE>
현재 인증된 사용자가 특정 작업을 할 수 있는지 확인하는 명령어
kubectl auth can-i <특정 작업> --all-namespaces
클러스터에게 권한을 부여하는 방식
특정 그룹에 권한을 주는 명령어
kubectl create clusterrolebinding <BINDING_NAME> \
--clusterrole=<CLUSTER_ROLE_NAME> \
--group=<KUBERNETES_GROUP_NAME>
이렇게 k8s 그룹을 만들고, 이후 IAM 사용자를 해당 그룹에 연결시켜주면 된다.
사용자 연결
aws eks create-access-entry \
--cluster-name <EKS_CLUSTER_NAME> \
--principal-arn "arn:aws:iam::<AWS_ACCOUNT_ID>:user/<IAM_USER_NAME>" \
--kubernetes-group <KUBERNETES_GROUP_NAME>
마이그레이션
기존의 aws-auth access entry 방식으로 바꾸고자 할 때
1. 기존 방식으로 사용자 등록
2. RBAC role, rolebinding으로 권한 부여
3. 같은 사용자로 access entry 생성 (aws eks create-access-entry)
4. 작동 확인
5. configmap에서 사용자 삭제
기존 사용자를 지우지 않고, 클러스터를 그대로 유지하면서 등록 방식만 새로운 방식으로 전환
IAM
IAM 사용자를 k8s 서비스 계정과 연결할 수 있다. 이 과정을 통해 pod가 aws api를 호출할 수 있다.
k8s 계정이 iam role을 사용할 수 있게 하려면, OIDC provider가 설정되어 있어야 한다.
IRSA 활성화
aws eks describe-cluster \
--name <클러스터 이름> \
--query cluster.identity.oidc.issuer \
--output text
OIDC provider를 IAM에 등록
eksctl utils associate-iam-oidc-provider \
--cluster <EKS_CLUSTER_NAME> \
--region <AWS_REGION> \ #이 줄은 생략 가능
--approve
이렇게 등록하면, iam 콘솔의 identity providers 페이지에서 생성된 것을 확인할 수 있다.
service account 생성
eksctl create iamserviceaccount \
--name <SERVICE_ACCOUNT_NAME> \
--namespace <NAMESPACE> \
--cluster <EKS_CLUSTER_NAME> \
--attach-policy-arn arn:aws:iam::aws:policy/<IAM_POLICY_NAME> \
--approve \
--override-existing-serviceaccounts
여기서 policy를 찾고 싶다면
aws iam list-policies --query 'Policies[?PolicyName==`AmazonS3ReadOnlyAccess`].Arn'
이런식으로 찾아서 쓰면 된다
limits & quotas
k8s에서 컴퓨팅, 클러스터 리소스를 관리
리소스 확인
kubectl -n <NAMESPACE> get deployment <DEPLOYMENT_NAME> -o yaml | yq '.spec.template.spec.containers[].resources'
리소스 지정
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: LimitRange
metadata:
name: <LIMIT_RANGE_NAME>
namespace: <NAMESPACE>
spec:
limits:
- default:
cpu: <LIMIT_CPU>
defaultRequest:
cpu: <REQUEST_CPU>
type: Container
EOF
limitrange는 이미 실행되고 있는 pod에는 적용되지 않기 때문에, 설정 후 rollout & restart를 통해 재시작해줘야 한다.
리소스를 생성해주는 것뿐만 아니라 최대/최소 사용 가능량을 지정해줄 수도 있다.
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: LimitRange
metadata:
name: <LIMIT_RANGE_NAME>
namespace: <NAMESPACE>
spec:
limits:
- default:
memory: <DEFAULT_LIMIT>
defaultRequest:
memory: <DEFAULT_REQUEST>
max:
memory: <MAX_MEMORY>
min:
memory: <MIN_MEMORY>
type: Container
EOF
네임스페이스 전체에 대해 리소스를 제한하는 방식
ResourceQuota
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ResourceQuota
metadata:
name: <RESOURCE_QUOTA_NAME>
namespace: <NAMESPACE>
spec:
hard:
requests.cpu: "<REQUESTS_CPU_LIMIT>"
requests.memory: <REQUESTS_MEMORY_LIMIT>
limits.cpu: "<LIMITS_CPU_LIMIT>"
limits.memory: <LIMITS_MEMORY_LIMIT>
EOF
Quota는 생성 당시 사용량 + 이후 생성 리소스의 합이 설정된 hard 한도를 넘는 순간 거절된다.
리소스가 아니라, 파드와 서비스 갯수에 대해서도 제한을 둘 수 있다.
kubectl -n <NAMESPACE> create resourcequota <QUOTA_NAME> --hard=pods=<POD_LIMIT>,services=<SERVICE_LIMIT>
Health Check
애플리케이션이 장애에서 빠르게 복구될 수 있도록하는 복원력을 갖도록 보장해야 한다.
이를 위해 애플리케이션 배포에서 liveness, readiness probe를 설정한다.

liveness probe : 컨테이너가 정상적으로 작동 중인지 주기적으로 확인하고, 실패 시 자동으로 재시작
readiness probe : 컨테이너가 트래픽을 받을 준비가 되었는지 확인하고, 준비되기 전까지는 Service가 트래픽을 전달하지 않음


AWS cloud watch container insights
EKS 클러스터에 add-on 설치
aws eks create-addon --addon-name amazon-cloudwatch-observability --cluster-name <클러스터 이름>
EKS 클러스터의 API, 인증, 감사 로그 등 모든 제어 로그를 CloudWatch로 전송하도록 설정
eksctl utils update-cluster-logging \
--enable-types all \
--region <AWS_REGION> \
--cluster <EKS_CLUSTER_NAME> \
--approve

콘솔에서 대시보드를 통해 성능을 확인할 수 있다.
Prometheus metric
지금까지 CloudWatch는 인프라 수준의 모니터링만 가능했지만, 컨테이너 내부 상태까지 보고 싶다면 Prometheus 연동이 필요하다.
cd ~/environment/<YOUR_WORKSHOP_DIRECTORY>/
kubectl apply -f <PROMETHEUS_AGENT_CONFIG_FILE>.yaml
Prometheus용 cloudwatch 에이전트를 설치한 후,
관리 콘솔에서,
📍 로그 확인
CloudWatch > Logs > Log Groups
/aws/containerinsights/eksworkshop-eksctl/prometheus
📍 메트릭 확인
CloudWatch > Metrics > Custom namespaces > ContainerInsights/Prometheus
로 모니터링이 가능하다.
AWS X-ray tracing
요청 흐름을 시각적으로 추적하고, 병목이나 오류 위치를 정확히 파악하기 위해 사용한다.
daemonset 배포
cd ~/environment/<YOUR_WORKSHOP_DIR>/
kubectl apply -f xray-eks.yaml
CloudWatch > X-Ray 에서 확인
Auto scaling
HPA
HPA(Horizontal Pod Autoscaler)는 CPU 사용률, 메모리, 또는 사용자 정의 메트릭을 기준으로 Kubernetes 파드 수를 자동으로 확장 또는 축소해주는 기능이다.
트래픽 증가 시 파드 수를 늘리고, 부하가 줄어들면 자동으로 줄이는 유연한 운영이 가능하게 만든다.
metrics server 설치
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
HPA 리소스 생성
kubectl autoscale deployment <DEPLOYMENT_NAME> -n <NAMESPACE> \
--cpu-percent=<TARGET_CPU_PERCENT> \
--min=<MIN_PODS> \
--max=<MAX_PODS>
모니터링
1. CLI로 상태 확인
kubectl get hpa -n <NAMESPACE> -w
관리 콘솔에서 확인
AWS Console → CloudWatch → Metrics → All Metrics
→ ContainerInsights > ClusterName, Namespace, Service
→ pod_cpu_utilization_over_pod_limit 메트릭 선택
KEDA
KEDA는 Kubernetes 클러스터에서 이벤트 기반 자동 확장(Event-driven autoscaling) 을 지원해주는 오픈소스 컴포넌트이다.
기존의 HPA(Horizontal Pod Autoscaler)는 CPU나 메모리 같은 리소스 사용량에만 기반해 확장을 결정했다면,
KEDA는 큐, 메시지 수, DB, 외부 서비스 메트릭 등 다양한 이벤트를 기반으로 파드 수를 자동으로 조절할 수 있다.
CA
EKS에서 노드 수를 자동으로 조절해주는 컴포넌트. Pod이 부족해서 Pending 상태가 되면 노드를 자동으로 늘리고, 사용량이 줄면 불필요한 노드를 줄여준다.
배포
cd ~/environment/<YOUR_PROJECT_PATH>/
kubectl apply -f <CLUSTER_AUTOSCALER_YAML_FILE>
카펜터
Karpenter는 AWS에서 제공하는 오픈소스 Kubernetes 노드 자동 프로비저닝 솔루션이다. 예약할 수 없는(Pending) Pod가 감지되면, 클러스터에 알맞은 EC2 인스턴스를 자동으로 생성하여 Pod를 수용한다. 필요 없는 노드는 자동으로 제거되어 리소스를 절약할 수 있다.
Nodeport
클러스터 외부에서 파드에 접근할 수 있도록 하는 가장 간단한 Kubernetes 서비스 유형이다.
NodePort는 각 워커 노드의 IP + 지정된 포트를 통해 파드에 접근할 수 있게 해준다.
서비스 생성 (.yaml)
apiVersion: v1
kind: Service
metadata:
name: frontendnp
namespace: workshop
spec:
type: NodePort
ports:
- nodePort: 30000
port: 80
targetPort: 9000
selector:
app: frontend
이렇게 해준 후, 보안 그룹에서의 인바운드 규칙에 포트번호에 따른 허용을 추가해줘야 한다.
Load Balancer
Kubernetes에서 클러스터 외부에 서비스를 노출하는 방식 중 하나이다.
NodePort는 노드 IP와 포트를 직접 사용해야 해서 불편한 점이 있지만, LoadBalancer는 외부 로드밸런서를 자동으로 생성하여 외부에서 접근할 수 있도록 해준다.
EKS에서는 AWS의 Elastic Load Balancer(NLB) 가 자동으로 생성된다.
서비스 생성 (.yaml)
apiVersion: v1
kind: Service
metadata:
name: frontend
namespace: workshop
annotations:
service.beta.kubernetes.io/aws-load-balancer-name : mynlb
service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: 9000
name: http
selector:
app: frontend
노드 포트와 다르게, 보안그룹 인바운드 규칙이 자동으로 설정되기 때문에 따로 열 필요는 없다.
Ingress
Ingress는 클러스터 외부에서 Kubernetes 서비스에 접근할 수 있도록 HTTP/HTTPS 기반 경로(Route) 를 정의하는 리소스이다. 여러 서비스를 하나의 LoadBalancer(ALB) 아래에서 경로(URL) 기준으로 분기할 수 있게 도와준다.
하지만 Ingress 리소스만 만든다고 끝이 아니라, 실제로 트래픽을 처리해줄 Ingress Controller가 반드시 함께 필요하다.
EKS에서는 AWS Load Balancer Controller를 활용해 ALB를 자동으로 프로비저닝한다.
리소스 예시
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: workshopingress
namespace: workshop
annotations:
alb.ingress.kubernetes.io/load-balancer-name: myalb
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/group.name: product-catalog
spec:
ingressClassName: alb
rules:
- http:
paths:
- path: /new
pathType: Prefix
backend:
service:
name: frontendnew
port:
number: 80
- path: /
pathType: Prefix
backend:
service:
name: frontend
port:
number: 80
보안그룹 따로 열 필요 없다!
이런식으로 yaml파일을 만들고, kubectl을 통해 배포하면 된다 (Nodeport, LB, ingress 공통)
kubectl apply -f <리소스>.yaml
Multi ingress
하나의 ALB에 여러 Ingress 리소스를 공유하는 방식이다.
SKU helm chart 배포
helm install sku ~/environment/eks-app-mesh-polyglot-demo/workshop/apps/sku/helm-chart/
ALB 공유 설정 확인
kubectl describe ingress sku -n sku
실행 결과 중, alb.ingress.kubernetes.io/group.name: <그룹 이름> 이 부분의 같은 group.name을 갖는 ingress들은 동일한 ALB를 공유하게 된다.
공유 ALB 확인 명령어
kubectl get ingress -A
TargetGroupBinding
AWS Load Balancer Controller에서 제공하는 리소스로, Kubernetes 서비스와 ALB Target Group을 연결할 수 있게 해준다. 이를 통해 ALB와 Kubernetes 리소스의 수명 주기를 분리할 수 있다.
TGB manifest (.yaml)
# testapp-tgb.yaml
apiVersion: elbv2.k8s.aws/v1beta1
kind: TargetGroupBinding
metadata:
name: testapptgb
namespace: workshop
spec:
serviceRef:
name: testapp
port: 80
targetGroupARN: <타겟그룹 ARN>
이후, TargetGroupBinding 리소스가 생성되면 testapp 서비스의 모든 파드가 해당 Target Group의 대상(Targets)으로 자동 연결된다.
Ingress나 LoadBalancer 없이도 기존 ALB의 Target Group만 활용하여 유연한 트래픽 라우팅이 가능하다.
EFS 파일 시스템
EFS 파일 시스템은 EKS 클러스터의 모든 워커 노드에서 공유 파일 시스템으로 접근할 수 있도록 해주는 NFS 기반 스토리지이다.
FILE_SYSTEM_ID=$(aws efs create-file-system | jq --raw-output '.FileSystemId')
TAG1=tag\:alpha.eksctl.io/cluster-name
TAG2=tag\:kubernetes.io/role/elb
subnets=($(aws ec2 describe-subnets \
--filters "Name=$TAG1,Values=$CLUSTER_NAME" "Name=$TAG2,Values=1" \
| jq --raw-output '.Subnets[].SubnetId'))
for subnet in ${subnets[@]}
do
echo "creating mount target in $subnet"
aws efs create-mount-target \
--file-system-id $FILE_SYSTEM_ID \
--subnet-id $subnet \
--security-groups $MOUNT_TARGET_GROUP_ID
done
EFS CSI 드라이버
EFS를 EKS에서 사용하려면 Amazon에서 제공하는 EFS CSI 드라이버를 설치해야 한다.
EFS는 Pod 간 공유 스토리지로 활용되며, NFS 기반이므로 동시에 여러 노드에서 접근 가능하다.
EFS CSI 설치
eksctl create iamserviceaccount \
--name efs-csi-controller-sa \
--namespace kube-system \
--cluster $CLUSTER_NAME \
--attach-policy-arn arn:aws:iam::aws:policy/service-role/AmazonEFSCSIDriverPolicy \
--approve
eksctl create addon \
--cluster $CLUSTER_NAME \
--name aws-efs-csi-driver \
--version latest \
--service-account-role-arn $(aws iam get-role --role-name AmazonEKS_EFS_CSI_DriverRole --query 'Role.Arn' --output text)
iamserviceaccount: EFS CSI 드라이버가 EFS에 접근할 수 있도록 권한을 가진 IAM 역할을 부여
addon: 실제 CSI 드라이버를 클러스터에 설치해주는 공식 EKS 애드온
PVC manifest
# EFS 파일시스템 ID를 PVC yaml에 삽입
EFS_ID=<efs-id> # 예: fs-0123abc456def
YAML_PATH=<your-pvc-yaml-path> # 예: ./efs-pvc.yaml
sed -i "s/EFS_VOLUME_ID/${EFS_ID}/g" ${YAML_PATH}
kubectl apply -f ${YAML_PATH}
PersistentVolume(PV): EFS 파일시스템을 쿠버네티스 내부에서 사용할 수 있게 연결
PersistentVolumeClaim(PVC): Pod가 필요한 스토리지 용량을 요청하는 객체
이 PVC를 사용하면 Pod에서 EFS를 볼륨처럼 마운트해서 사용 가능하다.
공유 스토리지 test : 복제본 확장
확장 명령어
kubectl scale deployment/<애플리케이션명> --replicas=2 -n <네임스페이스 이름>
EBS CSI
Kubernetes 환경에서 EBS 볼륨을 영구 스토리지로 사용할 수 있도록 해주는 표준 인터페이이다.
EBS CSI add-on
eksctl create addon \
--name aws-ebs-csi-driver \
--cluster <CLUSTER_NAME> \
--service-account-role-arn arn:aws:iam::<ACCOUNT_ID>:role/AmazonEKS_EBS_CSI_DriverRole \
--force
그리고 k8s가 '외부 시스템'과 연결되어야 하는 것이기 때문에 OIDC 인증으로 진행해야 한다!
Kubernetes에서 PVC를 생성할 때, EBS 볼륨을 자동으로 동적 생성하고 싶다면 먼저 EBS CSI 드라이버를 위한 StorageClass를 정의해야 한다.
cat << EoF > ${HOME}/environment/ebs_statefulset/mysql-storageclass.yaml
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: mysql-gp2
provisioner: ebs.csi.aws.com # Amazon EBS CSI driver
parameters:
type: gp2
encrypted: 'true' # EBS volumes will always be encrypted by default
volumeBindingMode: WaitForFirstConsumer #파드가 실제로 스케줄될 때 볼륨 생성
reclaimPolicy: Delete #pvc 삭제할 때 볼륨 같이 삭제됨
mountOptions:
- debug
EoF
storageclass 생성
kubectl create -f ${HOME}/environment/ebs_statefulset/mysql-storageclass.yaml
service 구성 두가지
cat << EoF > ${HOME}/environment/ebs_statefulset/mysql-services.yaml
# Headless service for stable DNS entries of StatefulSet members.
apiVersion: v1
kind: Service
metadata:
namespace: workshop
name: mysql
labels:
app: mysql
spec:
ports:
- name: mysql
port: 3306
clusterIP: None
selector:
app: mysql
---
# Client service for connecting to any MySQL instance for reads.
# For writes, you must instead connect to the leader: mysql-0.mysql.
apiVersion: v1
kind: Service
metadata:
namespace: workshop
name: mysql-read
labels:
app: mysql
spec:
ports:
- name: mysql
port: 3306
selector:
app: mysql
EoF
mySQL 생성
kubectl apply -f ~/environment/eks-app-mesh-polyglot-demo/workshop/mysql-statefulset.yaml
리소스 배포 확인
kubectl -n workshop rollout status statefulset mysql
파드들의 상태 변화를 모니터링 하고 싶다면
kubectl -n workshop get pods -l app=mysql --watch

관리 콘솔 ec2 > volumns > 'ebs'검색 으로도

동적으로 볼륨이 생성된 것을 확인할 수 있다.
k8s pod의 mysql-client를 사용하여 mysql 리더 파드에 접속해 sql 명령을 실행
kubectl -n workshop run mysql-client --image=mysql:5.7 -i --rm --restart=Never --\
mysql -h mysql-0.mysql.workshop <<EOF
CREATE DATABASE dev;
CREATE TABLE dev.product (prodId VARCHAR(120), prodName VARCHAR(120));
INSERT INTO dev.product (prodId,prodName) VALUES ('999','Mountain Bike');
EOF
리더는 mysql-0.mysql.workshop이라는 DNS 이름으로 접근 가능하고, 쓰기 작업은 반드시 리더에게 해야 하니까 이 주소로 접근해야 한다
리더와 팔로워의 구조에서 요청이 어느 노드로 분산되는지 확인하려면
kubectl -n workshop run mysql-client-loop --image=mysql:5.7 -i -t --rm --restart=Never --\
bash -ic "while sleep 1; do mysql -h mysql-read -e 'SELECT @@server_id,NOW()'; done"
애플리케이션이 read 서비스로 요청을 보내면, ready 상태의 모든 파드로 요청이 분산된다

이런식으로 id값이 바뀌는 걸 통해, 분산되고 있는 상황을 확인할 수 있다.
파드 갯수를 늘리고자 한다면
kubectl -n workshop scale statefulset mysql --replicas=3
를 통해 2 3으로 늘릴 수 있다.

파드가 하나 새롭게 늘어난 것 (id = 102) 을 확인할 수 있다.
그리고 각 파드가 가지고 있는 데이터를 확인하기 위해,
kubectl -n workshop run mysql-client --image=mysql:5.7 -it --rm --restart=Never --\
mysql -h mysql-2.mysql -e "SELECT * FROM dev.product"
이렇게 살펴볼 수 있는데 새로 늘어난 파드에 대한 부분을 보고 싶기 때문에 mysql-2로 입력하면 된다.
stateful에서 생성된 파드들은 이름에 순번이 붙기 때문에
첫번째 파드 : mysql-0
두번째 파드 : mysql-1
이런식으로 작성하면 됨!!
⚠️
만약 장애 상황에서, 파드 하나가 사라지더라도 statefulset의 역할을 지정된 수의 파드 복제본을 항상 유지하는 것이므로, 사라진 것과 동일한 이름을 가진 파드를 자동 복구 시켜준다!
워크샵 실습 전체를 해보진 못했지만, 한번도 실습해보지 못한 부분들 위주로 진행하면서 정리해보았다.

나 자신 고생했다 호달달