Kubernetes #1 Pod, Port-forward

Kubernetes #1 Pod, Port-forward #

#2025-09-08


1. 실습환경설정 #

  • 필요 패키지
    • kubectl, jq, curl, maven, Java
brew install kubectl jq curl maven
  • kubectl

    • Kubernetes 클러스터와 통신하는 CLI 도구
    • 쿠버네티스는 여러 개의 프로그램이 동시에 돌아가는 큰 시스템이고 여기에 지시를 내리는 도구.
  • Java 17

    • 여러 프로그램을 실행하는 공통 실행 환경(JVM)을 제공
    • 공통 실행 환경?
      • 여러 프로그램을 공통 언어로 사용하게해준다.
    • 프로그램들이 Java가 어디 있는지 알아야 하니까 JAVA_HOME이라는 환경 변수를 설정해준다.
export JAVA_HOME=/opt/homebrew/opt/openjdk@17

#

  • 클라우드 인증 정보, 커맨드 스크립트 다운로드
# 클라우드 인증 정보 wsl-install.tar 다운로드
curl -f -O https://*/wsl-install.tar (*: 주소 블라인드 처리)
tar xvf wsl-install.tar
kubectl get pod

# script.tar 다운로드
curl -f -O https://*script.tar (*: 주소 블라인드 처리)
tar xvf script.tar
chmod +x script/*
sudo mv script/* /usr/local/bin/
  • 자주 쓰는 커맨드 모음이라고 하는데 sh 파일들이 들어있었다 image

#

  • 셸 시작할 때 자동으로 실행되도록 환경 변수 설정
echo "source $HOME/dev.env" >> ~/.zshrc
echo "export JAVA_HOME=/opt/homebrew/opt/openjdk@17" >> ~/.zshrc
  • 터미널을 켤때마다 자동으로 설정이 적용되게.

#

2. 실습 코드 다운로드 #

$ pwd
/Users/yshmbid/Documents/home/github/Cloud/workspace/

curl -f -O https://.*/kubernetes.tar
tar xvf kubernetes.tar

curl -f -O https://.*/kubernetes.tar
tar xvf k8s-ddive.tar

#

  • 파일 구조
~/workspace/kubernetes
├── 00.container/
├── 01.pod/
│   └── (pod.yaml, 관련 코드들)
├── 02.deploy/
│   └── (deploy.yaml, 관련 코드들)
├── 03.kubectl/
├── 04.configmap/
├── 05.pvc/
├── 06.probe/
├── 07.blue-green/
├── env.properties
├── k8s-ddive.tar
├── kubernetes.tar
└── script.tar

#

3. 실습1 - Pod, Service, Deployment #

  • Harbor Image Registry
    • SKALA 환경에서는 Docker Hub 대신 자체적으로 관리하는 Harbor Image Registry 사용
      • Docker Hub가 전 세계가 공유하는 큰 창고라면 arbor는 특정 조직 내부에서 운영하는 전용 창고이고 각자가 만든 Docker 이미지를 올리고다운받을수있다.
    • 로그인 방법
      • 웹 콘솔 접속
      • CLI에서 docker login 명령으로 로그인
        • docker login amdp-registry.skala-ai.com/skala25a
        • robot 계정과 발급받은 토큰을 사용
    • 로그인 안하면?
      • 도커 이미지를 빌드하고 push할 때 인증 문제가 발생한다.
      • (chatgpt에 치면 dockerhub로 유도한다)

#

#1 Pod 배포

  • Pod는 Kubernetes에서 가장 작은 실행 단위.
    • 하나의 애플리케이션이 들어있다.
  • nginx라는 웹 서버 이미지를 Pod로 실행한다.
$ pwd
/Users/yshmbid/Documents/home/github/Cloud/workspace/kubernetes

# 1. Harbor Registry 로그인
$ docker login amdp-registry.skala-ai.com/skala25a
Authenticating with existing credentials...
Login Succeeded

# 2. Pod 배포 (kubectl run 방식)
$ kubectl run sk019-nginx \
>   --image=nginx \
>   --port=80 \
>   -n skala-practice
kubectl get pod sk019-nginx -n skala-practicepod/sk019-nginx created

$ kubectl get pod sk019-nginx -n skala-practice
NAME          READY   STATUS              RESTARTS   AGE
sk019-nginx   0/1     ContainerCreating   0          1s
  • kubectl run
    • 이름을 sk019-nginx로 지정
    • 사용할 이미지 nginx 설정
    • 80 포트를 열기
  • kubectl get pod
    • 해당 Pod가 잘 뜨는지 확인
  • 결과?
    • 컨테이너를 Kubernetes 환경 위에 올렸다.

#

#2 Pod 연결 (port-forward)

  • Pod가 실행됐으니까 외부에서 접속할수있게하려면?
    • port-forward로 로컬 PC의 특정 포트와 Pod 내부의 포트를 직접 연결한다. 예를 들어 로컬 8080 포트를 Pod의 80 포트와 연결하면 브라우저에서 localhost:8080으로 접속했을 때 Pod 안의 nginx 서버와 통신할 수 있다.
$ kubectl port-forward pod/sk019-nginx 8080:80 -n skala-practice &
[1] 1471
$ curl localhost:8080Forwarding from 127.0.0.1:8080 -> 80

#

#3 Service 연결

  • Pod는 Kubernetes에서 실행되는 최소단위인데 수명이 아주 짧다.
  • Pod가 죽으면 Kubernetes는 자동으로 새로운Pod를띄우는데 이때새로만들어진Pod는 이름이랑 IP주소가 달라진다.
    • 예를들어 오늘은 sk001-nginx라는 Pod에 10.0.1.3 같은 IP가 있었는데 내일은 sk001-nginx-abc123라는 새 Pod가 10.0.1.7 같은 주소를 가질수있고 그래서 Pod에 직접 붙는 방식은 오래쓸수가 없다.
  • Service는 특정 label(예: app=nginx)이 붙은 Pod들을 자동으로 찾아 연결해줘서 Pod가 교체되더라도 항상 같은 주소로 접속할 수 있게 해준다.
    • 예를들어 kubectl expose pod sk001-nginx --port=8080 --target-port=80를 하면 클러스터 안에서 8080 포트로 들어오는 요청을 자동으로 Pod의 80 포트로 전달해주는 Service가 생성된다.
      • Pod의 IP나 이름이 바뀌어도Service가 그걸 대신 추적해서 연결해줌.
  • 그래서 port-forward와 Service 연결의 차이?
    • port-forward는 임시로 내 PC와 특정 Pod 사이를 직접 연결하는 것 디버깅이나 빠른 테스트 때는 편리하지만 Pod가 재시작하면 연결이 끊긴다.
    • Service는 안정적인 네트워크 자원으로 Pod가 몇 번 바뀌든 항상 같은 주소로 접근할 수 있게 해준다.
$ kubectl expose pod sk019-nginx --port=8080 --target-port=80

# svc로 접속
$ kubectl port-forward svc/sk019-nginx 8080:8080 -n skala-practice

# cf) pod로 접속
$ kubectl port-forward pod/sk019-nginx 8080:80 -n skala-practice

#

#4 Pod manifest를 사용한 배포

  • 지금까지는 kubectl run 같은 명령어로 직접 Pod를 띄웠는데 예를 들어 kubectl run sk001-nginx –image=nginx라고 하면 곧바로 Pod가 생성되었다.
    • 이렇게하면 문제가 매번 명령어를 새로 쳐야 해서 사람이 실수할 수 있고 누군가는 포트를 빼먹고 누군가는 이름을 다르게 적어서 환경이 제각각이 될수있다.
  • manifest파일을 사용해서 pod가 어떤 이름을 가질지 어떤 이미지를 쓸지 몇 개를 띄울지 환경 변수는 뭔지 등을 작성하고 이를 사용해서 pod를만든다.
apiVersion: v1
kind: Pod
metadata:
  name: sk001-pod-test
  namespace: skala-practice
spec:
  containers:
  - name: nginx
    image: nginx:alpine
    ports:
    - containerPort: 80
    env:
    - name: USER_NAME
      value: sk001
  • pod.yaml

    • sk001-pod-test라는 이름의 Pod를 만드는데 안에는 nginx 컨테이너가 들어있고 80번 포트를 열고 USER_NAME이라는 환경 변수에 sk001을 넣는다.
  • env.properties

    • 설정값 세팅 파일
SERVICE_NAME="myfirst-api-server"

#***** NEVER Rewrite ****************************************
# java build 
JAR_FILE_PATH="./target/spring-boot-app-0.0.1-SNAPSHOT.jar"

# docker push를 위한 container registry 접속 정보
IMAGE_NAME="myfirst-api-server"
VERSION="1.0.0"

DOCKER_REGISTRY=* # (*: 블라인드 처리)
DOCKER_REGISTRY_USER=* 
DOCKER_REGISTRY_PASSWORD=*
DOCKER_CACHE="--no-cache"

DEPLOY_PATH="./k8s"
DEPLOY_FILE_NAME=deploy.yaml
DEPLOY_FILE_LIST="deploy.yaml service.yaml ingress.yaml deploy-with-config.yaml deploy-with-pvc.yaml pvc.yaml deploy-with-probe.yaml handle-pvc-pod.yaml"
# amd64 | arm64
CPU_PLATFORM=amd64
#***** NEVER REWRITE ****************************************


#------ USER Customization area --------------------------
USER_NAME=sk019
REPLICAS=1
NAMESPACE=skala-practice

CONTAINER_PORT=8080
LOGGING_LEVEL=DEBUG
#------ USER Customization area --------------------------
  • gen-yaml.sh
    • 원래는 pod.t만 있었고 gen-yaml.sh을써서 pod.yaml을 생성한다
apiVersion: v1
kind: Pod
metadata:
  name: ${USER_NAME}-pod-test
  namespace: ${NAMESPACE}
  labels:
    app: ${USER_NAME}-pod-test
spec:
  serviceAccountName: default
  containers:
  - name: nginx
    image:  nginx:mainline-alpine
    imagePullPolicy: Always
    env:
    - name: USER_NAME
      value: ${USER_NAME}
$ pwd
/Users/yshmbid/Documents/home/github/Cloud/workspace/kubernetes
$ gen-yaml.sh

#

#4 Pod manifest로 배포

# Pod 배포
$ kubectl apply -f pod.yaml -n skala-practice
pod/sk019-pod-test created
$ kubectl get pod -n skala-practice | grep sk019
sk019-nginx                                 1/1     Running             0               10m
sk019-pod-test                              0/1     ContainerCreating   0               1s
  • sk019-pod-test Pod 생성
  • 저때는 ContainerCreating이었는데 곧 Running됐을듯.
    • sk019-nginx Pod는 이전에생성한 nginx Pod.

#

#5 Pod 삭제 후 Deployment 배포, 재생성 확인

# Pod 삭제 후 Deployment 배포
$ kubectl delete -f pod.yaml -n skala-practice
pod "sk019-pod-test" deleted from skala-practice namespace

$ kubectl apply -f deploy.yaml -n skala-practice
deployment.apps/sk019-deploy-test created

$ kubectl get pod -n skala-practice | grep sk019
sk019-deploy-test-7d5b5cfd56-lk6m5          0/1     ContainerCreating   0               1s
sk019-nginx                                 1/1     Running             0               15m

# Pod 자동 재생성 확인
$ kubectl delete pod sk019-deploy-test-7d5b5cfd56-lk6m5 -n skala-practice
"sk019-deploy-test-7d5b5cfd56-lk6m5" deleted from skala-practice namespace

$ kubectl get pod -n skala-practice | grep sk019
sk019-deploy-test-7d5b5cfd56-l2djw          0/1     ContainerCreating   0               3s
sk019-nginx  
  • sk019-pod-test Pod를 지우고 deploy.yaml을 적용해서 sk019-deploy-test Deployment를 생성, Deployment가 내부적으로 새로운 Pod를 하나 띄운다.
    • deployment가 sk019-deploy-test-7d5b5cfd56-l2djw를 띄웠다.
  • sk019-deploy-test-7d5b5cfd56-lk6m5를 삭제하면?
    • 단일 pod으면 그냥없어지는데
    • Deployment로 관리되는 Pod는 Kubernetes가 “이 Deployment는 Pod 1개를 유지해야 해”라는 선언을 기억하고 있기 때문에 방금 삭제하자마자 새로운 Pod를 곧바로 생성한다.
      • 지웠는데도 sk019-deploy-test-7d5b5cfd56-l2djw가 ContainerCreating. (곧 Running)

#

4. 실습2 - 쿠버네티스 배포 #

#1 Spring Boot 컨테이너 만들기

# 01.springboot-v1.0에서 작업
$ pwd
/Users/yshmbid/Documents/home/github/Cloud/workspace/kubernetes/00.container/01.springboot-v1.0

# jar 빌드
$ mvn clean install -DskipTests
  • JAR 빌드
    • Maven으로 jar 빌드
    • 수행하면 target/ 아래에 spring-boot-app-0.0.1-SNAPSHOT.jar 같이 JAR가 생긴다
# 이미지 빌드, 푸시
$ ./docker-build.sh
$ ./docker-push.sh 
  • 도커 이미지 빌드, 푸시
    • 왜 push가 필요하냐면 쿠버네티스 노드가 이미지를 가져갈 주소가 Harbor 레지스트리이기 때문이야 로컬 도커 데몬에만 있으면 클러스터가 못 본다.
    • 뭔말이냐면
      • 내가 노트북에서 docker build로 이미지를 만들면 결과물은 내 로컬 도커 엔진 안에만 저장돼있고 내 컴퓨터 안에서만 그 이미지를 쓸 수 있는데
        • 쿠버네티스 클러스터의 Pod는 내 노트북에서 실행되는 게 아니라 클러스터 안의 노드 서버들에서 실행된다. 쿠버네티스가 Pod를 만들 때 nginx:latest 이미지를 가져와서 컨테이너를 띄워라 « 이렇게 노드에 지시하는데
        • 여기서 노드는 이미지를 가져올 저장소 주소가 필요하다. 기본적으로는 Docker Hub 같은 공개 레지스트리를 보거나 따로 지정된 Harbor 같은 사설 레지스트리를 본다.
        • 내가 만든 이미지를 Harbor 레지스트리에 push하지 않으면 이미지가 노트북 로컬 Docker 안에만 있으니 쿠버네티스 노드들(클러스터)은 그 이미지를 찾을 수 없고 Pod 상태가 ImagePullBackOff로 빠진다.
    • 결론
      • build만 하면 내 노트북 안에만 있고
      • push까지 해야 Harbor 레지스트리에 올라가서 쿠버네티스 노드들이 거기서 이미지를 pull해서 컨테이너를 실행할 수 있다.

#

#2 FastAPI 컨테이너 만들기

# 02.python-v2.0에서 작업
$ pwd
/Users/yshmbid/Documents/home/github/Cloud/workspace/kubernetes/00.container/02.python-v2.0

# 도커 이미지 빌드, 푸시
$ ./docker-build.sh
$ ./docker-push.sh 

#

#3 Harbor에 정상 등록됐는지 확인

https://amdp-registry.skala-ai.com/ 접속해서 떠있는지보기.

image

#

#4 쿠버네티스 배포

  • 배포?
    • 내가 만든 이미지를 클러스터에서 실행 가능한 애플리케이션으로 올리기.
# 작업 위치
$ pwd
/Users/yshmbid/Documents/home/github/Cloud/workspace/kubernetes

# Deployment 배포 실행
$ kubectl apply -f 02.deploy/deploy.yaml
deployment.apps/sk019-myfirst-api-server unchanged

# Pod 상태 확인
$ kubectl get pod | grep sk019
sk019-myfirst-api-server-57fddcd6c8-l4jms      1/1     Running             0             4m27s
  • 배포정보

    • deploy.yaml
      • 어떤이미지를 쓸건지 (image: amdp-registry…/sk019/myapp:latest)
      • 몇 개의 Pod를 유지할 건지 (replicas: 1)
      • 어떤 포트를 열 건지 (containerPort: 8080)
      • 라벨(sk019-myfirst-api-server)
  • 배포명령실행

  1. kubectl은 API Server에 deploy.yaml 내용을 전달
  2. API Server는 etcd(쿠버네티스 데이터 저장소)에 “이런 Deployment를 유지하라”라는 선언을 저장
  3. 스케줄러가 클러스터 노드 중 하나를 선택, 해당 노드의 kubelet이 “이 Pod는 이 이미지를 써야 해”라고 파악한 뒤, 컨테이너 런타임(docker/containerd)이 Harbor 레지스트리에서 이미지를 pull해온다.
  4. 이미지가 잘 내려받아지면 컨테이너가 시작되고, Pod 상태가 Running으로 바뀐다.

#

  • 네트워크 구성, pofr-forward 실행
    • Pod는 내부 IP가 매번 바뀌기 때문에 Pod가 뜨더라도 외부에서 바로 접속할 수는 없고 그래서 service.yaml로 Service 리소스를 만들고 label을 기준으로 Pod와 연결.
# Service 생성
$ kubectl apply -f 02.deploy/service.yaml
service/sk019-myfirst-api-server unchanged

# Service 확인
$ kubectl get service | grep sk019
sk019-myfirst-api-server            ClusterIP      10.100.83.86     <none> 

# Service 포트포워딩 실행
$ kubectl port-forward svc/sk019-myfirst-api-server 8080:8080
Forwarding from 127.0.0.1:8080 -> 8080
Forwarding from [::1]:8080 -> 8080
  • 포트 포워딩

    • Service가 생겼다면, 로컬에서 테스트할 수 있도록 포트를 터널링한다. http://localhost:8080/api/users로 접속하면, 사실은 클러스터 안 Pod까지 트래픽이 전달된다.
  • http://localhost:8080 접속해보면 제대로 뜬다.

image

#

포트포워딩 의문점1 - 포트 포워딩이 그래서 하는것은?

  • 쿠버네티스 Pod는 클러스터 내부 네트워크(IP 대역)에서만 접근 가능하고 내 노트북 브라우저에서 직접 Pod IP를 찍어도 접근이 안됨. 즉 내 PC -> 쿠버네티스 클러스터로 가는 길은 막혀있다.
  • port-forward는 임시 터널로써 kubectl port-forward 명령을 쓰면 내 PC의 포트와 클러스터 안 리소스(Pod 또는 Service)의 포트를 직접연결한다.
    • kubectl port-forward svc/sk019-service 8080:8080하면 내 PC 로컬 8080 포트로 들어오는 요청을 클러스터 안 sk019-service의 8080 포트로 바로 보내는 터널을 만든다.

포트포워딩 의문점2 - http://localhost:8080/api/users로 접근하면 클러스터 안 Pod까지 간다?

  • 포트포워딩이 걸린 상태에서 http://localhost:8080/api/users로 접속하면
    • 브라우저는 “로컬 8080”으로 요청을 보냄
    • kubectl이 이 요청을 가져가서 클러스터 안 Service -> Pod으로 전달
    • Pod 안의 Spring Boot 애플리케이션이 /api/users 요청을 처리하고 응답을 돌려줌
    • 응답이 다시 포트포워딩을 통해 내 PC의 브라우저로옴
  • 결과적으로는
    • 내 PC localhost:8080에 접속한 것처럼 보이지만, 실제 “처리"는 클러스터 안 Pod가 한다.
  • 결과적으로는에서 말하는 “처리"란?
    • 브라우저 주소창에 http://localhost:8080/api/users를 입력 -> 브라우저가 HTTP 요청 패킷을 생성해서 내 PC의 8080 포트로 보냄 -> kubectl port-forward가 이 요청을 받아서 클러스터 안으로 전달(Kubernetes Service안으로 던짐)
    • 클러스터 안에서?
      • Service가 label로 연결된 Pod를 찾아서 트래픽을 넘김(label로 연결된 Pod = Spring Boot 컨테이너가 들어 있는 Pod) -> Pod 안에는 내가 만든 Spring Boot 애플리케이션이 실행 중.
    • Pod 안에서?
      • 컨테이너 안에서 Java 프로세스가 떠 있고, 8080 포트를 열어놓고 있다. Spring Boot는 /api/users라는 URL 요청을 Controller 클래스에 매핑해 둔다. 예를 들어 UserController라는 클래스에 @GetMapping("/api/users”)가 있다면, 요청이 오면 그 메서드가 실행되고 JSON 응답(예: [{id:1, name:“Alice”}, {id:2, name:“Bob”}])을 생성해서 HTTP 응답으로 내보낸다. Pod가 만든 응답은 Service -> port-forward 터널 -> 내 PC의 localhost:8080을 거쳐 브라우저로 돌아온다.
  • 결론
    • 브라우저 입장에서는 그냥 로컬에서 프로그램이 실행된 것처럼 보이지만 실제로는 클러스터 안 Pod가 로직을 수행하고 응답을 돌려준것.
      • 요청 = /api/users
      • 처리 = Spring Boot 애플리케이션이 Controller/Service/Repository를 통해 데이터 조회/가공
      • 응답 = JSON 결과를 브라우저로 반환

#