Overview
이번주는 CloudNet@ 스터디에서 진행하시는 DOIK(Database Operator In Kubernetes) 스터디 1주차이다.
EKS 원클릭 배포
# YAML 파일 다운로드
curl -O https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/EKS/eks-oneclick.yaml
# CloudFormation 스택 배포
예시) aws cloudformation deploy --template-file eks-oneclick.yaml --stack-name myeks --parameter-overrides KeyName=somazkey SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32 MyIamUserAccessKeyID=AKIA5... MyIamUserSecretAccessKey='CVNa2...' ClusterBaseName=myeks --region ap-northeast-2
# CloudFormation 스택 배포 완료 후 작업용 EC2 IP 출력
aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[0].OutputValue' --output text
43.201.5.150
작업용 EC2 에 SSH 접속 (SSH 키 파일 사용) : 쿠버네티스 정상 설치 확인
← 스택 생성 시작 후 20분 후 접속 할 것
# 작업용 EC2 SSH 접속
ssh -i ~/.ssh/somazkey.pem ec2-user@$(aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[0].OutputValue' --output text)
(somaz@myeks:N/A) [root@myeks-bastion-EC2 ~]#
# cloud-init 실행 과정 로그 확인
tail -f /var/log/cloud-init-output.log
# cloud-init 정상 완료 후 eksctl 실행 과정 로그 확인
tail -f /root/create-eks.log
# 설치 확인
kubectl cluster-info
eksctl get cluster
eksctl get nodegroup --cluster $CLUSTER_NAME
# 환경변수 정보 확인
export | egrep 'ACCOUNT|AWS_|CLUSTER|KUBERNETES|VPC|Subnet'
export | egrep 'ACCOUNT|AWS_|CLUSTER|KUBERNETES|VPC|Subnet' | egrep -v 'SECRET|KEY'
# 인증 정보 확인
cat /root/.kube/config | yh
kubectl config view | yh
kubectl ctx
# 노드 정보 확인
kubectl get node --label-columns=node.kubernetes.io/instance-type,eks.amazonaws.com/capacityType,topology.kubernetes.io/zone
eksctl get iamidentitymapping --cluster myeks
# krew 플러그인 확인
kubectl krew list
kubectl resource-capacity
# default 네임스페이스 적용
kubectl ns default
# 모든 네임스페이스에서 모든 리소스 확인
kubectl get-all
워커 노드 정보 확인 및 SSH 접속
# 노드 IP 확인 및 PrivateIP 변수 지정
aws ec2 describe-instances --query "Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,PrivateIPAdd:PrivateIpAddress,InstanceName:Tags[?Key=='Name']|[0].Value,Status:State.Name}" --filters Name=instance-state-name,Values=running --output table
N1=$(kubectl get node --label-columns=topology.kubernetes.io/zone --selector=topology.kubernetes.io/zone=ap-northeast-2a -o jsonpath={.items[0].status.addresses[0].address})
N2=$(kubectl get node --label-columns=topology.kubernetes.io/zone --selector=topology.kubernetes.io/zone=ap-northeast-2b -o jsonpath={.items[0].status.addresses[0].address})
N3=$(kubectl get node --label-columns=topology.kubernetes.io/zone --selector=topology.kubernetes.io/zone=ap-northeast-2c -o jsonpath={.items[0].status.addresses[0].address})
echo "export N1=$N1" >> /etc/profile
echo "export N2=$N2" >> /etc/profile
echo "export N3=$N3" >> /etc/profile
echo $N1, $N2, $N3
192.168.1.46, 192.168.2.25, 192.168.3.203
# 보안그룹 ID와 보안그룹 이름(Name아님을 주의!) 확인
aws ec2 describe-security-groups --query 'SecurityGroups[*].[GroupId, GroupName]' --output text
# 노드 보안그룹 ID 확인
aws ec2 describe-security-groups --filters Name=group-name,Values=*ng1* --query "SecurityGroups[*].[GroupId]" --output text
NGSGID=$(aws ec2 describe-security-groups --filters Name=group-name,Values=*ng1* --query "SecurityGroups[*].[GroupId]" --output text)
echo $NGSGID
# 노드 보안그룹에 eksctl-host 에서 노드(파드)에 접속 가능하게 룰(Rule) 추가 설정
aws ec2 authorize-security-group-ingress --group-id $NGSGID --protocol '-1' --cidr 192.168.1.100/32
# eksctl-host 에서 노드의IP나 coredns 파드IP로 ping 테스트
ping -c 2 $N1
ping -c 2 $N2
ping -c 2 $N3
# 워커 노드 SSH 접속 : '-i ~/.ssh/id_rsa' 생략 가능
for node in $N1 $N2 $N3; do ssh ec2-user@$node hostname; done
ssh ec2-user@$N1
exit
ssh ec2-user@$N2
exit
ssh ec2-user@$N3
exit
설치 후 Add-on 정보 확인
# eksctl 설치/업데이트 addon 확인
eksctl get addon --cluster $CLUSTER_NAME
NAME VERSION STATUS ISSUES IAMROLE UPDATE AVAILABLE CONFIGURATION VALUES
aws-ebs-csi-driver v1.23.1-eksbuild.1 CREATING 0 arn:aws:iam::61184xxxxxxx:role/eksctl-myeks-addon-aws-ebs-csi-driver-Role1-1Q3GLPAVLF3KD
coredns v1.10.1-eksbuild.4 ACTIVE 0
kube-proxy v1.27.6-eksbuild.2 ACTIVE 0
vpc-cni v1.15.1-eksbuild.1 ACTIVE 0 arn:aws:iam::61184xxxxxxx:role/eksctl-myeks-addon-vpc-cni-Role1-LLWD34XVXOU9
기본 설정 작업
- `AWS LB Ctrl` 설치, `ExternalDNS` 설치, `kube-ops-view` 설치, `스토리지 클래스` 생성, `AWS Cert Manager` 인증서 확인
AWS Load Balancer Controller 설치 - 링크
# 설치
helm repo add eks https://aws.github.io/eks-charts
helm repo update
helm install aws-load-balancer-controller eks/aws-load-balancer-controller -n kube-system --set clusterName=$CLUSTER_NAME \
--set serviceAccount.create=false --set serviceAccount.name=aws-load-balancer-controller
# 설치 확인
kubectl get crd
kubectl get deployment -n kube-system aws-load-balancer-controller
kubectl describe deploy -n kube-system aws-load-balancer-controller
ExternalDNS 설치 - 링크
MyDomain=<자신의 도메인>
echo "export MyDomain=<자신의 도메인>" >> /etc/profile
MyDomain=somaz.link
echo "export MyDomain=somaz.link" >> /etc/profile
MyDnsHostedZoneId=$(aws route53 list-hosted-zones-by-name --dns-name "${MyDomain}." --query "HostedZones[0].Id" --output text)
echo $MyDomain, $MyDnsHostedZoneId
somaz.link, /hostedzone/Z03204211VEUZG9O0RLE5
curl -s -O https://raw.githubusercontent.com/cloudneta/cnaeblab/master/_data/externaldns.yaml
MyDomain=$MyDomain MyDnsHostedZoneId=$MyDnsHostedZoneId envsubst < externaldns.yaml | kubectl apply -f -
kube-ops-view 설치
helm repo add geek-cookbook https://geek-cookbook.github.io/charts/
helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set env.TZ="Asia/Seoul" --namespace kube-system
kubectl patch svc -n kube-system kube-ops-view -p '{"spec":{"type":"LoadBalancer"}}'
kubectl annotate service kube-ops-view -n kube-system "external-dns.alpha.kubernetes.io/hostname=kubeopsview.$MyDomain"
echo -e "Kube Ops View URL = http://kubeopsview.$MyDomain:8080/#scale=1.5"
Kube Ops View URL = http://kubeopsview.somaz.link:8080/#scale=1.5
# 확인 및 로그 모니터링
kubectl get pod -l app.kubernetes.io/name=external-dns -n kube-system
NAME READY STATUS RESTARTS AGE
external-dns-595f6dc454-467jd 1/1 Running 0 84s
k get svc -n kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP
PORT(S) AGE
aws-load-balancer-webhook-service ClusterIP 10.100.92.90 <none>
443/TCP 41m
kube-dns ClusterIP 10.100.0.10 <none>
53/UDP,53/TCP 55m
kube-ops-view LoadBalancer 10.100.163.152 aaa652ecd61c74a00ab377e0d8aa4e88-101488255.ap-northeast-2.elb.amazonaws.com 8080:30475/TCP 46s
kubectl logs deploy/external-dns -n kube-system -f
스토리지 클래스 생성
# gp3 스토리지 클래스 생성
kubectl apply -f https://raw.githubusercontent.com/gasida/DOIK/main/1/gp3-sc.yaml
# 스토리지 클래스 확인
kubectl get sc
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
gp2 (default) kubernetes.io/aws-ebs Delete WaitForFirstConsumer false 17m
gp3 ebs.csi.aws.com Delete WaitForFirstConsumer true 5s
AWS Cert Manager 인증서 확인
# ACM 인증서 확인
aws acm list-certificates
# ACM 인증서 변수 선언
CERT_ARN=`aws acm list-certificates --query 'CertificateSummaryList[].CertificateArn[]' --output text`; echo $CERT_ARN
arn:aws:acm:ap-northeast-2:6118xxxxxxxx:certificate/75e6fb4f-5999-4701-80f2-ab94e015bc9b
프로메테우스-스택 설치
모니터링에 필요한 여러 요소를 단일 차트(스택)으로 제공 ← 시각화(그라파나) 등 - Helm
설치
# 모니터링
kubectl create ns monitoring
watch kubectl get pod,pvc,svc,ingress -n monitoring
# 사용 리전의 인증서 ARN 확인
CERT_ARN=`aws acm list-certificates --query 'CertificateSummaryList[].CertificateArn[]' --output text`
echo $CERT_ARN
# repo 추가
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
# 파라미터 파일 생성
cat <<EOT > monitor-values.yaml
prometheus:
prometheusSpec:
podMonitorSelectorNilUsesHelmValues: false
serviceMonitorSelectorNilUsesHelmValues: false
retention: 5d
retentionSize: "10GiB"
scrapeInterval: '15s'
evaluationInterval: '15s'
ingress:
enabled: true
ingressClassName: alb
hosts:
- prometheus.$MyDomain
paths:
- /*
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}, {"HTTP":80}]'
alb.ingress.kubernetes.io/certificate-arn: $CERT_ARN
alb.ingress.kubernetes.io/success-codes: 200-399
alb.ingress.kubernetes.io/load-balancer-name: myeks-ingress-alb
alb.ingress.kubernetes.io/group.name: study
alb.ingress.kubernetes.io/ssl-redirect: '443'
grafana:
defaultDashboardsTimezone: Asia/Seoul
adminPassword: prom-operator
ingress:
enabled: true
ingressClassName: alb
hosts:
- grafana.$MyDomain
paths:
- /*
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}, {"HTTP":80}]'
alb.ingress.kubernetes.io/certificate-arn: $CERT_ARN
alb.ingress.kubernetes.io/success-codes: 200-399
alb.ingress.kubernetes.io/load-balancer-name: myeks-ingress-alb
alb.ingress.kubernetes.io/group.name: study
alb.ingress.kubernetes.io/ssl-redirect: '443'
defaultRules:
create: false
kubeEtcd:
enabled: false
alertmanager:
enabled: false
EOT
# 배포
helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack --version 51.7.0 \
-f monitor-values.yaml --namespace monitoring
# 확인
## operator : 시스템 경고 메시지 정책(prometheus rule), 애플리케이션 모니터링 대상 추가 등의 작업을 편리하게 할수 있게 CRD 지원
## prometheus-0 : 모니터링 대상이 되는 파드는 ‘exporter’라는 별도의 사이드카 형식의 파드에서 모니터링 메트릭을 노출, pull 방식으로 가져와 내부의 시계열 데이터베이스에 저장
## node-exporter : 노드익스포터는 물리 노드에 대한 자원 사용량(네트워크, 스토리지 등 전체) 정보를 메트릭 형태로 변경하여 노출
## kube-state-metrics : 쿠버네티스의 클러스터의 상태(kube-state)를 메트릭으로 변환하는 파드
## grafana : 프로메테우스는 메트릭 정보를 저장하는 용도로 사용하며, 그라파나로 시각화 처리
helm list -n monitoring
kubectl get pod,svc,ingress -n monitoring
kubectl get-all -n monitoring
kubectl get prometheus,servicemonitors -n monitoring
kubectl get crd | grep monitoring
확인
# 프로메테우스 ingress 확인
kubectl get ingress -n monitoring kube-prometheus-stack-prometheus
kubectl describe ingress -n monitoring kube-prometheus-stack-prometheus
# 프로메테우스 ingress 도메인으로 웹 접속
echo -e "Prometheus Web URL = https://prometheus.$MyDomain"
# 그라파나 ingress 확인
kubectl get ingress -n monitoring kube-prometheus-stack-grafana
kubectl describe ingress -n monitoring kube-prometheus-stack-grafana
# 그라파나 ingress 도메인으로 웹 접속 : 기본 계정 - admin / prom-operator
echo -e "Grafana Web URL = https://grafana.$MyDomain"
삭제 방법
eksctl delete cluster --name $CLUSTER_NAME && aws cloudformation delete-stack --stack-name $CLUSTER_NAME
쿠버네티스 특징
쿠버네티스 아키텍처
`Kubernetes Components` : K8S 클러스터는 Controle Plane(마스터)와 Node(노드)로 구성 - 링크
선언형(멱등성) 실습
쿠버네티스의 선언형 접근법과 멱등성은 쿠버네티스의 핵심 원칙 중 하나이다.
선언형 접근법 (Declarative Approach)
- 선언형 접근법에서는 원하는 시스템의 최종 상태를 선언하고, 시스템이 그 상태를 달성하도록 자동으로 변경한다.
- 예를 들어, 쿠버네티스에서 파드의 수를 5개로 유지하고 싶다면, 원하는 상태를 선언하기만 하면 쿠버네티스가 이를 관리한다. 이 접근법의 장점은 시스템의 현재 상태를 직접 관리할 필요가 없다는 것 이다.
멱등성 (Idempotence)
- 멱등성은 동일한 연산을 여러 번 적용하더라도 동일한 결과를 가져오는 성질을 의미한다.
- 쿠버네티스의 선언형 접근법은 멱등성을 가져야 한다.
- 즉, 동일한 구성을 여러 번 적용하더라도 시스템의 최종 상태는 동일해야 한다.
- 예를 들어, 동일한 파드 구성을 여러 번 적용하더라도, 최종적으로 원하는 파드의 수와 상태가 유지되어야 한다.
# 터미널1 (모니터링)
watch -d 'kubectl get pod'
# 터미널2
# Deployment 배포(Pod 3개)
kubectl create deployment my-webs --image=nginx --replicas=3
kubectl get pod -w
# 파드 증가 및 감소
kubectl scale deployment my-webs --replicas=6 && kubectl get pod -w
kubectl scale deployment my-webs --replicas=3
kubectl get pod
# 강제로 파드 삭제 : 바라는상태 + 선언형에 대한 대략적인 확인! ⇒ 어떤 일이 벌어지는가?
kubectl delete pod --all && kubectl get pod -w
kubectl get pod
# 실습 완료 후 Deployment 삭제
kubectl delete deploy my-webs
Amazon EKS 소개
Amazon Elastic Kubernetes Service는 자체 Kubernetes 컨트롤 플레인 또는 노드를 설치, 운영 및 유지 관리할 필요 없이 Kubernetes 실행에 사용할 수 있는 관리형 서비스
EKS 아키텍처
- EKS 컨트롤 플레인 : AWS Managed VPC - 3개 AZ, API NLB, ETCD ELB - 링크
- 그림 출처 : https://awskoreamarketingasset.s3.amazonaws.com/2022 Summit/pdf/T14S4_Amazon EKS 마이그레이션 요점 정리.pdf
ETCD란?
Kubernetes는 etcd모든 클러스터 데이터의 기본 스토리지로 사용된다. Kubernetes 클러스터 상태를 저장하고 복제하는 데 도움이 된다.
- 서비스 검색 : Kubernetes의 포드, 서비스 및 기타 워크로드는 시간이 지남에 따라 변경될 수 있는 IP 주소를 얻는다. `etcd` 서비스 검색에 중요한 서비스 및 해당 위치에 대한 일관된 보기를 제공한다.
- 구성 : ConfigMap 및 Secrets와 같은 구성 세부정보는 `etcd` 에 저장된다.
- API 서버 : 모든 Kubernetes는 api-server시스템의 전체 상태를 알고 있으며 이 데이터를 `etcd`에 저장한다.
쿠버네티스 스토리지
- 파드 내부의 데이터는 파드가 정지되면 모두 삭제됨 → 즉, 파드가 모두 상태가 없는(Stateless) 애플리케이션이였다. Temporary filesystem, Volume - 링크
데이터베이스(파드)처럼 데이터 보존이 필요 == 상태가 있는(Stateful) 애플리케이션 : PV & PVC → 로컬 볼륨(hostPath) ⇒ 퍼시스턴트 볼륨(Persistent Volume, PV) - 어느 노드에서도 연결하여 사용 가능, 예시) NFS, AWS EBS, Ceph 등
파드가 생성될 때 자동으로 볼륨을 마운트하여 파드에 연결하는 기능을 동적 프로비저닝(Dynamic Provisioning)이라고 한다.
퍼시스턴트 볼륨의 사용이 끝났을 때 해당 볼륨은 어떻게 초기화할 것인지 별도로 설정할 수 있는데, 쿠버네티스는 이를 Reclaim Policy 라고 부른다.
Reclaim Policy 에는 크게 Retain(보존), Delete(삭제, 즉 EBS 볼륨도 삭제됨) 방식이 있습니다.
스토리지 소개
- 출처 : (🧝🏻♂️) 김태민 기술 블로그 - 링크
`볼륨`
- emptyDir, hostPath, PV/PVC
`다양한 볼륨사용`
- K8S 자체 제공(hostPath, local), 온프렘 솔루션(ceph 등), NFS, 클라우드 스토리지(AWS EBS 등)
`동적 프로비저닝` & 볼륨 상태 , `ReclaimPolicy`
AWS EBS Controller
- Volume (ebs-csi-controller) 소개 : EBS CSI driver 동작 : 볼륨 생성 및 파드에 볼륨 연결 - 링크
- persistentvolume, persistentvolumeclaim의 accessModes는 ReadWriteOnce로 설정해야 한다 - Why?
- accessModes는 PV와 PVC에서 스토리지에 어떻게 접근할 것인지를 정의한다.
- 주요 accessModes에는 ReadWriteOnce (RWO), ReadOnlyMany (ROX), ReadWriteMany (RWX)가 있다.
- ReadWriteOnce (RWO)는 스토리지 볼륨에 단일 노드에서 읽기/쓰기로 접근할 수 있음을 의미한다.
- EBS스토리지 기본 설정이 동일 AZ에 있는 EC2 인스턴스(에 배포된 파드)에 연결해야 한다 - Why? 파드 스케줄링 방안은?
- AWS EBS 볼륨은 특정 AZ에서 프로비저닝되며 해당 AZ 내의 EC2 인스턴스만 해당 볼륨에 연결할 수 있다.
- EBS 볼륨을 다른 AZ의 EC2 인스턴스에 연결하려면 볼륨 스냅샷을 생성하고 해당 스냅샷을 사용하여 새 AZ에서 볼륨을 생성해야 한다
- persistentvolume, persistentvolumeclaim의 accessModes는 ReadWriteOnce로 설정해야 한다 - Why?
PVC/PV 파드 테스트
# 워커노드의 EBS 볼륨 확인 : tag(키/값) 필터링 - 링크
aws ec2 describe-volumes --filters Name=tag:Name,Values=$CLUSTER_NAME-ng1-Node --output table
aws ec2 describe-volumes --filters Name=tag:Name,Values=$CLUSTER_NAME-ng1-Node --query "Volumes[*].Attachments" | jq
aws ec2 describe-volumes --filters Name=tag:Name,Values=$CLUSTER_NAME-ng1-Node --query "Volumes[*].{ID:VolumeId,Tag:Tags}" | jq
aws ec2 describe-volumes --filters Name=tag:Name,Values=$CLUSTER_NAME-ng1-Node --query "Volumes[].[VolumeId, VolumeType, Attachments[].[InstanceId, State][]][]" | jq
aws ec2 describe-volumes --filters Name=tag:Name,Values=$CLUSTER_NAME-ng1-Node --query "Volumes[].{VolumeId: VolumeId, VolumeType: VolumeType, InstanceId: Attachments[0].InstanceId, State: Attachments[0].State}" | jq
# 워커노드에서 파드에 추가한 EBS 볼륨 확인
aws ec2 describe-volumes --filters Name=tag:ebs.csi.aws.com/cluster,Values=true --output table
aws ec2 describe-volumes --filters Name=tag:ebs.csi.aws.com/cluster,Values=true --query "Volumes[*].{ID:VolumeId,Tag:Tags}" | jq
aws ec2 describe-volumes --filters Name=tag:ebs.csi.aws.com/cluster,Values=true --query "Volumes[].{VolumeId: VolumeId, VolumeType: VolumeType, InstanceId: Attachments[0].InstanceId, State: Attachments[0].State}" | jq
# 워커노드에서 파드에 추가한 EBS 볼륨 모니터링
while true; do aws ec2 describe-volumes --filters Name=tag:ebs.csi.aws.com/cluster,Values=true --query "Volumes[].{VolumeId: VolumeId, VolumeType: VolumeType, InstanceId: Attachments[0].InstanceId, State: Attachments[0].State}" --output text; date; sleep 1; done
# PVC 생성
cat <<EOT > awsebs-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: ebs-claim
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 4Gi
storageClassName: gp3
EOT
kubectl apply -f awsebs-pvc.yaml
kubectl get pvc,pv
# 파드 생성
cat <<EOT > awsebs-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: app
spec:
terminationGracePeriodSeconds: 3
containers:
- name: app
image: centos
command: ["/bin/sh"]
args: ["-c", "while true; do echo \$(date -u) >> /data/out.txt; sleep 5; done"]
volumeMounts:
- name: persistent-storage
mountPath: /data
volumes:
- name: persistent-storage
persistentVolumeClaim:
claimName: ebs-claim
EOT
kubectl apply -f awsebs-pod.yaml
# PVC, 파드 확인
kubectl get pvc,pv,pod
kubectl get VolumeAttachment
# 추가된 EBS 볼륨 상세 정보 확인
aws ec2 describe-volumes --volume-ids $(kubectl get pv -o jsonpath="{.items[0].spec.csi.volumeHandle}") | jq
# PV 상세 확인 : nodeAffinity 내용의 의미는?
kubectl get pv -o yaml | yh
...
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: topology.ebs.csi.aws.com/zone
operator: In
values:
- ap-northeast-2b
...
kubectl get node --label-columns=topology.ebs.csi.aws.com/zone,topology.kubernetes.io/zone
kubectl describe node | more
# 파일 내용 추가 저장 확인
kubectl exec app -- tail -f /data/out.txt
# 아래 명령어는 확인까지 다소 시간이 소요됨
kubectl df-pv
PV NAME PVC NAME NAMESPACE NODE NAME POD NAME VOLUME MOUNT NAME SIZE USED AVAILABLE %USED IUSED IFREE %IUSED
pvc-4cbf6134-73c7-478b-a2e3-c535b293304e ebs-claim default ip-192-168-3-203.ap-northeast-2.compute.internal app persistent-storage 3Gi 24Ki 3Gi 0.00 11 262133 0.00
## 파드 내에서 볼륨 정보 확인
kubectl exec -it app -- sh -c 'df -hT --type=overlay'
kubectl exec -it app -- sh -c 'df -hT --type=ext4'
Filesystem Type Size Used Avail Use% Mounted on
/dev/nvme1n1 ext4 3.9G 16M 3.8G 1% /data
볼륨 증가 - 링크 ⇒ 늘릴수는 있어도 줄일수는 없다! - 링크
# 현재 pv 의 이름을 기준하여 4G > 10G 로 증가 : .spec.resources.requests.storage의 4Gi 를 10Gi로 변경
kubectl get pvc ebs-claim -o jsonpath={.spec.resources.requests.storage} ; echo
kubectl get pvc ebs-claim -o jsonpath={.status.capacity.storage} ; echo
kubectl patch pvc ebs-claim -p '{"spec":{"resources":{"requests":{"storage":"10Gi"}}}}'
kubectl patch pvc ebs-claim -p '{"status":{"capacity":{"storage":"10Gi"}}}' # status 는 바로 위 커멘드 적용 후 EBS 10Gi 확장 후 알아서 10Gi 반영됨
# 확인 : 볼륨 용량 수정 반영이 되어야 되니, 수치 반영이 조금 느릴수 있다
kubectl exec -it app -- sh -c 'df -hT --type=ext4'
Filesystem Type Size Used Avail Use% Mounted on
/dev/nvme1n1 ext4 9.8G 28K 9.8G 1% /data
kubectl df-pv
PV NAME PVC NAME NAMESPACE NODE NAME POD NAME VOLUME MOUNT NAME SIZE USED AVAILABLE %USED IUSED IFREE %IUSED
pvc-4cbf6134-73c7-478b-a2e3-c535b293304e ebs-claim default ip-192-168-3-203.ap-northeast-2.compute.internal app persistent-storage 9Gi 28Ki 9Gi 0.00 12 655348 0.00
aws ec2 describe-volumes --volume-ids $(kubectl get pv -o jsonpath="{.items[0].spec.csi.volumeHandle}") | jq
Pod 삭제 후 재생성해도 데이터가 남아있는지 확인
kubectl delete pod app
pod "app" deleted
kubectl apply -f awsebs-pod.yaml
pod/app created
kubectl exec app -- tail -f /data/out.txt
Sun Oct 15 12:39:34 UTC 2023
Sun Oct 15 12:39:39 UTC 2023
Sun Oct 15 12:39:44 UTC 2023
Sun Oct 15 12:39:49 UTC 2023
Sun Oct 15 12:39:54 UTC 2023
Sun Oct 15 12:39:59 UTC 2023
Sun Oct 15 12:40:04 UTC 2023
Sun Oct 15 12:40:09 UTC 2023
Sun Oct 15 12:40:51 UTC 2023
Sun Oct 15 12:40:56 UTC 2023
삭제
kubectl delete pod app
kubectl get pvc,pv
kubectl delete pvc ebs-claim
쿠버네티스 네트워크
AWS VPC CNI
[실습] 테스트용 파드 생성
# 테스트용 파드 netshoot-pod 생성
cat <<EOF | kubectl create -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: netshoot-pod
spec:
replicas: 3
selector:
matchLabels:
app: netshoot-pod
template:
metadata:
labels:
app: netshoot-pod
spec:
containers:
- name: netshoot-pod
image: nicolaka/netshoot
command: ["tail"]
args: ["-f", "/dev/null"]
terminationGracePeriodSeconds: 0
EOF
# 파드 이름 변수 지정
PODNAME1=$(kubectl get pod -l app=netshoot-pod -o jsonpath={.items[0].metadata.name})
PODNAME2=$(kubectl get pod -l app=netshoot-pod -o jsonpath={.items[1].metadata.name})
PODNAME3=$(kubectl get pod -l app=netshoot-pod -o jsonpath={.items[2].metadata.name})
# 파드 확인
kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE
NOMINATED NODE READINESS GATES
netshoot-pod-84d58d854-5xfdb 1/1 Running 0 22s 192.168.1.59 ip-192-168-1-46.ap-northeast-2.compute.internal <none> <none>
netshoot-pod-84d58d854-krb58 1/1 Running 0 22s 192.168.3.206 ip-192-168-3-203.ap-northeast-2.compute.internal <none> <none>
netshoot-pod-84d58d854-vg6t6 1/1 Running 0 22s 192.168.2.239 ip-192-168-2-25.ap-northeast-2.compute.internal <none> <none>
kubectl get pod -o=custom-columns=NAME:.metadata.name,IP:.status.podIP
- 파드가 생성되면, 워커 노드에 eniY@ifN 추가되고 라우팅 테이블에도 정보가 추가된다
테스트용 파드 접속(exec) 후 확인
# 테스트용 파드 접속(exec) 후 Shell 실행
kubectl exec -it $PODNAME1 -- zsh
# 아래부터는 pod-1 Shell 에서 실행 : 네트워크 정보 확인
----------------------------
ip -c addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
3: eth0@if12: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc noqueue state UP group default
link/ether e2:21:7e:5c:f2:52 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 192.168.1.59/32 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::e021:7eff:fe5c:f252/64 scope link
valid_lft forever preferred_lft forever
ip -c route
route -n
ping -c 2 <pod-2 IP> # 192.168.2.25
ex) ping -c 2 192.168.2.25
PING 192.168.2.25 (192.168.2.25) 56(84) bytes of data.
64 bytes from 192.168.2.25: icmp_seq=1 ttl=254 time=2.51 ms
64 bytes from 192.168.2.25: icmp_seq=2 ttl=254 time=1.02 ms
--- 192.168.2.25 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1002ms
rtt min/avg/max/mdev = 1.024/1.766/2.509/0.742 ms
ps
cat /etc/resolv.conf
exit
----------------------------
# 파드2 Shell 실행
kubectl exec -it $PODNAME2 -- ip -c addr
# 파드3 Shell 실행
kubectl exec -it $PODNAME3 -- ip -br -c addr
[실습] 파드에서 외부 통신 테스트 및 확인
# 작업용 EC2 : pod-1 Shell 에서 외부로 ping
kubectl exec -it $PODNAME1 -- ping -c 1 www.google.com
kubectl exec -it $PODNAME1 -- ping -i 0.1 www.google.com
# 워커 노드 EC2 : 퍼블릭IP 확인
ssh ec2-user@$N1 curl -s ipinfo.io/ip ; echo
3.35.51.76
ssh ec2-user@$N2 curl -s ipinfo.io/ip ; echo
ssh ec2-user@$N3 curl -s ipinfo.io/ip ; echo
# 작업용 EC2 : pod-1 Shell 에서 외부 접속 확인 - 공인IP는 어떤 주소인가?
## The right way to check the weather - 링크
kubectl exec -it $PODNAME1 -- curl -s ipinfo.io/ip ; echo
kubectl exec -it $PODNAME1 -- curl -s wttr.in/seoul
kubectl exec -it $PODNAME1 -- curl -s wttr.in/seoul?format=3
kubectl exec -it $PODNAME1 -- curl -s wttr.in/Moon
kubectl exec -it $PODNAME1 -- curl -s wttr.in/:help
다음 실습을 위해서 파드 삭제: `kubectl delete deploy netshoot-pod`
Service란?
Service는 쿠버네티스 클러스터 내부를 외부에 노출해준다.
- ClusterIP
- NodePort
- LoadBalancer
- Service(LoadBalancer Controller) : AWS Load Balancer Controller + NLB IP 모드 동작 with AWS VPC CNI
`ClusterIP`
`NodePort`
`LoadBalancer`
`Service(LoadBalancer Controller)` : AWS Load Balancer Controller + NLB IP 모드 동작 with AWS VPC CNI
[실습] 서비스/파드 배포 테스트 with NLB
# 작업용 EC2 - 디플로이먼트 & 서비스 생성
curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/2/echo-service-nlb.yaml
cat echo-service-nlb.yaml | yh
kubectl apply -f echo-service-nlb.yaml
# 확인
kubectl get deploy,pod
kubectl get svc,ep,ingressclassparams,targetgroupbindings
kubectl get targetgroupbindings -o json | jq
# AWS ELB(NLB) 정보 확인
aws elbv2 describe-load-balancers | jq
aws elbv2 describe-load-balancers --query 'LoadBalancers[*].State.Code' --output text
# 웹 접속 주소 확인
kubectl get svc svc-nlb-ip-type -o jsonpath={.status.loadBalancer.ingress[0].hostname} | awk '{ print "Pod Web URL = http://"$1 }'
# 파드 로깅 모니터링
kubectl logs -l app=deploy-websrv -f
# 분산 접속 확인
NLB=$(kubectl get svc svc-nlb-ip-type -o jsonpath={.status.loadBalancer.ingress[0].hostname})
curl -s $NLB
for i in {1..100}; do curl -s $NLB | grep Hostname ; done | sort | uniq -c | sort -nr
52 Hostname: deploy-echo-55456fc798-2w65p
48 Hostname: deploy-echo-55456fc798-cxl7z
# 지속적인 접속 시도 : 아래 상세 동작 확인 시 유용(패킷 덤프 등)
while true; do curl -s --connect-timeout 1 $NLB | egrep 'Hostname|client_address'; echo "----------" ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; done
AWS NLB의 대상 그룹 확인 : IP를 확인해보자
- 파드 2개 → 1개 → 3개 설정 시 동작 : auto discovery ← 어떻게 가능할까?
# 작업용 EC2 - 파드 1개 설정
kubectl scale deployment deploy-echo --replicas=1
# 확인
kubectl get deploy,pod,svc,ep
curl -s $NLB
for i in {1..100}; do curl -s --connect-timeout 1 $NLB | grep Hostname ; done | sort | uniq -c | sort -nr
# 작업용 EC2 - 파드 3개 설정
kubectl scale deployment deploy-echo --replicas=3
# 확인
kubectl get deploy,pod,svc,ep
curl -s $NLB
for i in {1..100}; do curl -s --connect-timeout 1 $NLB | grep Hostname ; done | sort | uniq -c | sort -nr
- - 실습 리소스 삭제: `kubectl delete deploy deploy-echo; kubectl delete svc svc-nlb-ip-type`
Ingress란?
클러스터 내부의 서비스(ClusterIP, NodePort, Loadbalancer)를 외부로 노출(HTTP/HTTPS) - Web Proxy 역할
- AWS Load Balancer Controller + Ingress (ALB) IP 모드 동작 with AWS VPC CNI 를 사용
[실습] 서비스/파드 배포 테스트 with Ingress(ALB) - ALB
# 게임 파드와 Service, Ingress 배포
curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/3/ingress1.yaml
cat ingress1.yaml | yh
kubectl apply -f ingress1.yaml
# 모니터링
watch -d kubectl get pod,ingress,svc,ep -n game-2048
# 생성 확인
kubectl get-all -n game-2048
kubectl get ingress,svc,ep,pod -n game-2048
kubectl get targetgroupbindings -n game-2048
NAME SERVICE-NAME SERVICE-PORT TARGET-TYPE AGE
k8s-game2048-service2-f0a415c192 service-2048 80 ip 2m32s
# Ingress 확인
kubectl describe ingress -n game-2048 ingress-2048
# 게임 접속 : ALB 주소로 웹 접속
kubectl get ingress -n game-2048 ingress-2048 -o jsonpath={.status.loadBalancer.ingress[0].hostname} | awk '{ print "Game URL = http://"$1 }'
Game URL = http://k8s-game2048-ingress2-70d50ce3fd-1856533181.ap-northeast-2.elb.amazonaws.com
# 파드 IP 확인
kubectl get pod -n game-2048 -owide
- ALB 대상 그룹에 등록된 대상 확인 : ALB에서 파드 IP로 직접 전달
파드 3개로 증가
# 터미널1
watch kubectl get pod -n game-2048
# 터미널2 : 파드 3개로 증가
kubectl scale deployment -n game-2048 deployment-2048 --replicas 3
파드 1개로 감소
# 터미널2 : 파드 1개로 감소
kubectl scale deployment -n game-2048 deployment-2048 --replicas 1
실습 리소스 삭제
kubectl delete ingress ingress-2048 -n game-2048
kubectl delete svc service-2048 -n game-2048 && kubectl delete deploy deployment-2048 -n game-2048 && kubectl delete ns game-2048
ExternalDns란?
K8S 서비스/인그레스 생성 시 도메인을 설정하면, AWS(Route 53), Azure(DNS), GCP(Cloud DNS) 에 레코드 자동 생성/삭제한다.
ExternalDNS CTRL 권한 주는 방법 3가지 : Node IAM Role, Static credentials, IRSA
CoreDns란?
CoreDNS는 유연하고 확장 가능한 DNS 서버로서, 특히 쿠버네티스 환경에서 서비스 디스커버리에 사용되는 도구이다. 쿠버네티스에서는 기존의 kube-dns를 대체하기 위해 CoreDNS를 기본 DNS 시스템으로써 도입되었다.
스테이트풀셋 & 헤드리스서비스
Stateless vs Stateful 비교 및 스테이트풀셋 소개
`Stateless`와 `Stateful`은 애플리케이션 또는 시스템의 동작 방식과 데이터의 지속성에 관한 용어이다.
Stateless
- 애플리케이션 또는 시스템이 이전의 요청 또는 트랜잭션에서의 상태 정보를 기억하지 않고 각 요청을 별개의 단위로 처리하는 것을 의미한다.
- 각 요청은 독립적으로 처리되며, 이전 또는 이후의 요청에 영향을 주지 않는다.
Stateful
- 애플리케이션 또는 시스템이 이전의 요청 또는 트랜잭션에서의 상태 정보를 유지하고, 이 정보를 이용해 후속 요청을 처리하는 것을 의미한다.
- 상태 정보는 메모리, 디스크, 또는 외부 데이터베이스 등에 저장될 수 있다.
이와 반대로 쿠버네티스에서 상태가 존재하는 포드를 지칭할 때는 '애완동물'에 비유한다.
애완동물에는 특별한 이름을 붙여주기 때문에 다른 애완동물과 명확히 구분된다.
따라서 사람의 입장에서 애완동물은 대체 불가능한 개체로서, 항상 고유한 식별자를 갖는 것으로 여겨진다.
이와 같은 이유로 쿠버네티스에서는 상태를 갖는 애플리케이션, 즉 스테이트풀셋을 통해 생성되는 포드를 보통 애완동물에 비유한다.
상태를 갖는 각 포드는 모두 고유하며, 쉽게 대체될 수 없기 때문다.
스테이트풀셋 & 헤드리스서비스 실습
# 모니터링
watch -d 'kubectl get sts,pods,svc,pvc,pv'
# 스테이트풀셋 & 헤드리스서비스 배포
curl -s -O https://raw.githubusercontent.com/kubernetes/website/main/content/en/examples/application/web/web.yaml
cat web.yaml | yh
kubectl apply -f web.yaml && kubectl get pods -w -l app=nginx
# 파드 hostname 확인
for i in 0 1; do kubectl exec "web-$i" -- sh -c 'hostname'; done
kubectl df-pv
# netshoot 이미지로 netdebug 파드에 zsh 실행
kubectl run -it --rm netdebug --image=nicolaka/netshoot --restart=Never -- zsh
--------------------
nslookup nginx
nslookup -type=srv nginx
nslookup web-0.nginx
nslookup web-1.nginx
exit
--------------------
# 파드 삭제 실행 후 재실행 시 생성 순서 확인
kubectl delete pod -l app=nginx && kubectl get pods -w -l app=nginx
# 파드 hostname 확인
for i in 0 1; do kubectl exec web-$i -- sh -c 'hostname'; done
# netshoot 이미지로 netdebug 파드에 zsh 실행
kubectl run -it --rm netdebug --image=nicolaka/netshoot --restart=Never -- zsh
--------------------
nslookup nginx
Server: 10.100.0.10
Address: 10.100.0.10#53
Name: nginx.default.svc.cluster.local
Address: 192.168.1.19
Name: nginx.default.svc.cluster.local
Address: 192.168.3.84
nslookup -type=srv nginx
Server: 10.100.0.10
Address: 10.100.0.10#53
nginx.default.svc.cluster.local service = 0 50 80 web-0.nginx.default.svc.cluster.local.
nginx.default.svc.cluster.local service = 0 50 80 web-1.nginx.default.svc.cluster.local.
nslookup web-0.nginx
Server: 10.100.0.10
Address: 10.100.0.10#53
Name: web-0.nginx.default.svc.cluster.local
Address: 192.168.3.84
nslookup web-1.nginx
exit
--------------------
# PVC 확인
kubectl get pvc -l app=nginx
kubectl get pvc -l app=nginx
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-web-0 Bound pvc-0c499e39-d55d-4e38-9c7a-ef8ec58c6730 1Gi RWO gp2 116s
www-web-1 Bound pvc-c2b76cc8-f9e4-4c47-8e63-a15491dd5287 1Gi RWO gp2 104s
# 웹 서버 index.html 에 hostname 추가 후 웹 접속 해서 확인
# 웹 서버 파드는 볼륨 마운트 정보 : Mounts: /usr/share/nginx/html from www (rw) - PersistentVolumeClaim
for i in 0 1; do kubectl exec "web-$i" -- sh -c 'echo "$(hostname)-pv-test" > /usr/share/nginx/html/index.html'; done
for i in 0 1; do kubectl exec -i -t "web-$i" -- curl http://localhost/; done
# 파드 삭제 실행 후 재실행 시 생성 순서 확인
kubectl delete pod -l app=nginx && kubectl get pods -w -l app=nginx
# 웹 접속 해서 확인 : PV 저장소 확인
for i in 0 1; do kubectl exec -i -t "web-$i" -- curl http://localhost/; done
# 파드 증가
kubectl scale sts web --replicas=5 && kubectl get pods -w -l app=nginx
# 파드 감소
kubectl patch sts web -p '{"spec":{"replicas":3}}' && kubectl get pods -w -l app=nginx
# 삭제
kubectl delete -f web.yaml && kubectl delete pvc --all
(실습 완료 후) 자원 삭제
- 삭제 방안 : 장점(1줄 명령어로 완전 삭제), 단점(삭제 실행과 완료까지 SSH 세션 유지 필요)
eksctl delete cluster --name $CLUSTER_NAME && aws cloudformation delete-stack --stack-name $CLUSTER_NAME
Reference
- AWS Load Balancer Controller 설치 - 링크
- ExternalDNS 설치 - 링크
- Kubernetes Components : K8S 클러스터는 Controle Plane(마스터)와 Node(노드)로 구성 - 링크
- 그림 출처 : https://awskoreamarketingasset.s3.amazonaws.com/2022 Summit/pdf/T14S4_Amazon EKS 마이그레이션 요점 정리.pdf
- 쿠버네티스 스토리지 링크
- 스토리지 소개 (🧝🏻♂️) 김태민 기술 블로그 - 링크
- AWS VPC CNI Github Proposal
'교육, 커뮤니티 후기 > DOIK (데이터베이스 오퍼레이터 인 쿠버네티스) 스터디' 카테고리의 다른 글
DOIK 1주차 - 도전과제1(Deploying Cassandra with a StatefulSet) + 도전과제4(kui) (4) | 2023.10.19 |
---|