컨피그맵은 파드의 컨테이너에서 참조하여 사용하게 된다. 환경 변수로 전달하는 방법과 볼륨으로 마운트하여 사용하는 방법이 있다.

  • 환경 변수로 전달
    • 컨피그맵의 특정 키 전달: spec.containers[].env[].valueForm.configMapKeyRef.name/key
    • 컨피그맵 전체 키 전달: spec.containers[].envFrom[].configMapRef.name
  • 볼륨으로 마운트
    • 컨피그맵의 특정 키 마운트: spec.volumes[].configMap.items[].key/path
    • 컨피그맵 전체 키 마운트: spec.volumes[].configMap.name

1. 환경 변수로 특정 키 전달

아래 매니페스트와 같이 env[] 에 환경변수들을 이름과 값으로 하나씩 직접 지정한다. sample-configmap 이라는 컨피그맵에 connection.max 키로 담긴 값을 CONNECTION_MAX 라는 환경변수로 세팅하는 것이다.

apiVersion: v1
kind: Pod
metadata:
  name: sample-configmap-single-env
spec:
  containers:
    - name: configmap-container
      image: nginx:1.27
      env:
        - name: CONNECTION_MAX
          valueFrom:
            configMapKeyRef:
              name: sample-configmap
              key: connection.max
$ kubectl apply -f sample-configmap-single-env.yaml
$ kubectl exec -it sample-configmap-single-env -- env | grep CONNECTION_MAX
CONNECTION_MAX=100

 

2. 환경 변수로 전체 키 전달

직접 환경변수명과 불러올 컨피그맵 값을 지정하지 않고 컨피그맵 전체를 환경변수로 그대로 등록하는 방법이다. 간단하긴 하지만, 매니페스트만 봐서는 어떤 환경변수가 설정되는지 알 수 없다는 단점이 있다.

아래 매니페스트처럼 간단히 envFrom[] 에 가져올 컨피그맵들의 참조를 걸어주면 된다.

apiVersion: v1
kind: Pod
metadata:
  name: sample-configmap-multi-env
spec:
  containers:
    - name: configmap-container
      image: nginx:1.27
      envFrom:
        - configMapRef:
            name: sample-configmap
$ kubectl apply -f sample-configmap-multi-env.yaml
$ kubectl exec -it sample-configmap-multi-env -- env
...
connection.max=100
connection.min=10
nginx.conf=user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;

sample.properties=property.1=value-1
property.2=value-2
property.3=value-3

test.sh=#!/bin/bash
echo "Hello, kubernetes"
sleep infinity

thread=16
...

그러나 위처럼 파드에 설정된 환경변수를 출력해보면 nginx.conf, sample.properties, test.sh 가 제대로 설정되지 못했음을 알 수 있다.(개행 처리가 의도대로 되지 않음) 개행된 값을 컨테이너로 주입하고자 할 때는 볼륨 마운트 방식으로 사용하여야 한다. 아래와 같은 주의사항을 참고한다.

  • 환경 변수명에 "." 또는 "-" 가 포함되지 않도록 한다.
  • 환경 변수 값이 개행을 포함하지 않도록 한다.(yaml 문법의 <key>: | 개행 스타일 포함)

 

3. 볼륨으로 특정 키 마운트

컨피그맵의 정보를 컨테이너의 볼륨으로 마운트 하는 방식으로, spec.containers[].volumeMounts[] 에 마운트 할 볼륨명과 마운트 경로를 지정하고, spec.volumes[] 에 생성할 볼륨명과 값을 가져올 컨피그맵 및 읽을 key, 그리고 마운트 경로에 생성할 파일명(path)을 지정한다. 직접 마운트 파일명을 지정한다는 것이 특징이다.

다음과 같은 매니페스트로 생성한다.

apiVersion: v1
kind: Pod
metadata:
  name: sample-configmap-single-volume
spec:
  containers:
    - name: configmap-container
      image: nginx:1.27
      volumeMounts:
        - name: config-volume
          mountPath: /config
  volumes:
    - name: config-volume
      configMap:
        name: sample-configmap
        items:
          - key: nginx.conf
            path: nginx-sample.conf
$ kubectl apply -f sample-configmap-single-volume.yaml
$ kubectl exec -it sample-configmap-single-volume -- cat /config/nginx-sample.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;

 

4. 볼륨으로 전체 키 마운트

컨피그맵의 전체 정보를 컨테이너의 볼륨으로 마운트하는 방식으로, spec.containers[].volumeMounts[] 에 마운트 할 볼륨명과 마운트 경로를 지정하고, spec.volumes[] 에 생성할 볼륨명과 가져올 컨피그맵을 지정한다. 마운트 경로에 생성되는 파일명은 컨피그맵의 key 와 동일하다. 매니페스트는 간단해지지만 매니페스트만으로는 컨피그맵의 어떤 key-value가 마운트되는지 알 수 없다는 단점이 있다.

다음과 같은 매니페스트로 생성한다.

apiVersion: v1
kind: Pod
metadata:
  name: sample-configmap-multi-volume
spec:
  containers:
    - name: configmap-container
      image: nginx:1.27
      volumeMounts:
        - name: config-volume
          mountPath: /config
  volumes:
    - name: config-volume
      configMap:
        name: sample-configmap
$ kubectl apply -f sample-configmap-multi-volume.yaml
$ kubectl exec -it sample-configmap-multi-volume -- ls /config
connection.max	connection.min	nginx.conf  sample.properties  test.sh	thread
$ kubectl exec -it sample-configmap-multi-volume -- paste /config/connection.max /config/nginx.conf /config/sample.properties
100	user nginx;	property.1=value-1
	worker_processes auto;	property.2=value-2
	error_log /var/log/nginx/error.log;	property.3=value-3
	pid /run/nginx.pid;

 

블로그 이미지

망원동똑똑이

프로그래밍 지식을 자유롭게 모아두는 곳입니다.

,

ConfigMap 은 key-value 들을 저장하는 리소스이다. value 에는 바이너리 데이터나 nginx.conf 같은 설정 내용도 저장할 수 있다. 하나의 ConfigMap에는 1MB 까지 저장 가능하다. 생성하는 방법은 Generic 타입의 Secret 과 거의 동일하며, 3가지 생성하는 방법을 설명한다.

 

1. 매니페스트에서 생성

일반적인 리소스 생성 방식과 동일하게 kubectl apply -f <매니페스트파일명> 또는 kubectl create -f <매니페스트파일명> --save-config 으로 생성하는 방식이다. 시크릿과는 달리 base64 인코딩되지 않는다. value 를 여러 행으로 저장하는 경우(nginx 설정파일 내용 등) YAML 문법대로 <key>: | 로 시작하여 여러 행에 정의한다. 숫자는 큰따옴표로 둘러쌓아야 한다. 아래 예시를 참고한다.

apiVersion: v1
kind: ConfigMap
metadata:
  name: sample-configmap
data:
  thread: "16"
  connection.max: "100"
  connection.min: "10"
  sample.properties: |
    property.1=value-1
    property.2=value-2
    property.3=value-3
  nginx.conf: |
    user nginx;
    worker_processes auto;
    error_log /var/log/nginx/error.log;
    pid /run/nginx.pid;
  test.sh: |
    #!/bin/bash
    echo "Hello, kubernetes"
    sleep infinity

 

아래 커맨드로 저장된 key-value 를 확인해본다.

$ kubectl get configmaps sample-configmap -o yaml
apiVersion: v1
data:
  connection.max: "100"
  connection.min: "10"
  nginx.conf: |
    user nginx;
    worker_processes auto;
    error_log /var/log/nginx/error.log;
    pid /run/nginx.pid;
  sample.properties: |
    property.1=value-1
    property.2=value-2
    property.3=value-3
  test.sh: |
    #!/bin/bash
    echo "Hello, kubernetes"
    sleep infinity
  thread: "16"
kind: ConfigMap
...

 

2. 리터럴 값을 전달하여 생성

kubectl 커맨드로 시크릿을 생성하되, --from-literal 옵션으로 직접 key-value 쌍을 전달하는 방식이다. 아래처럼 생성한다.

# 생성
$ kubectl create configmap --save-config web-config \
  --from-literal=connection.max=100 --from-literal=connection.min=10

# 확인
$ kubectl get cm web-config -o json | jq '.data'
{
  "connection.max": "100",
  "connection.min": "10"
}

 

3. 파일의 내용을 value 로 생성

kubectl 커맨드로 생성하되, --from-file 옵션으로 key-value 로 사용될 파일들을 지정하는 방식이다. 파일명이 key 가 되고, 파일 내용이 value 가 된다. 만약 파일명 대신 다른 key 를 사용하고자 한다면 --from-file=<사용할key>=<파일명> 형식으로 입력한다.

# 생성
$ kubectl create configmap --save-config sample-configmap-file \
> --from-file=./nginx.conf

# 확인
$ kubectl get configmaps sample-configmap-file -o json | jq '.data'
{
  "nginx.conf": "user  nginx;\nworker_processes  auto;\nerror_log  /var/log/nginx/error.log warn;\npid        /var/run/nginx.pid;\n"
}

# describe 확인
$ kubectl describe configmaps sample-configmap-file
Name:         sample-configmap-file
Namespace:    default
Labels:       <none>
Annotations:  <none>

Data
====
nginx.conf:
----
user  nginx;
worker_processes  auto;
error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;



BinaryData
====

Events:  <none>

 

k8s 에 실제 등록하지 않고 매니페스트 파일로 출력하려면 --dry-run=client -o yaml 옵션을 주어 실행한다. UTF-8 이외의 데이터를 저장하면 자동으로 binaryData 필드로 base64 인코딩 되어 저장된다.

$ kubectl create configmap sample-configmap-binary-2 \
  --from-file=image.png \
  --from-literal=index.html="Hello, Kubernetes" \
  --dry-run=client -o yaml \
  > sample-configmap-binary-2.yaml
# sample-configmap-binary-2.yaml 매니페스트
apiVersion: v1
binaryData:
  image.png: iVBORw0KGgoAAAANSUhEUgAAAs...<생략>
data:
  index.html: Hello, Kubernetes
kind: ConfigMap
metadata:
  creationTimestamp: null
  name: sample-configmap-binary-2

이제 이를 기반으로 ConfigMap 을 생성한 후, 아래 매니페스트로 ConfigMap 을 볼륨 마운트 한 파드를 생성한 후, curl 로 호출해보자

apiVersion: v1
kind: Pod
metadata:
  name: sample-configmap-binary-webserver
spec:
  containers:
    - name: nginx-container
      image: nginx:1.27
      volumeMounts:
        - name: config-volume
          mountPath: /usr/share/nginx/html
  volumes:
    - name: config-volume
      configMap:
        name: sample-configmap-binary-2
# ConfigMap 생성
$ kubectl apply -f sample-configmap-binary-2.yaml

# Pod 생성
$ kubectl apply -f sample-configmap-binary-webserver.yaml

# 확인을 위한 포트 포워딩
$ kubectl port-forward sample-configmap-binary-webserver 8080:80

# 호출 확인
$ curl http://localhost:8080/index.html
Hello, Kubernetes

# 볼륨 마운트 경로에 파일 생성 확인
$ kubectl exec -it sample-configmap-binary-webserver -- ls /usr/share/nginx/html
image.png  index.html

 

블로그 이미지

망원동똑똑이

프로그래밍 지식을 자유롭게 모아두는 곳입니다.

,

Secret 을 파드에서 사용중인 상태라면, Secret 이 수정되었을 때 자동으로 파드에 수정된 값이 반영될까? 이는 파드에서 Secret 을 사용하는 방식에 따라 다르다.

방식 시크릿 수정시 반영 방법
환경 변수 파드 재시작
볼륨 마운트 최대 1분(기본값) 이내 자동 반영

환경 변수 방식과 볼륨 마운트 방식에서 Secret 수정을 반영하는 방법을 각각 알아본다.

 

1. 환경 변수 방식

기본적으로 Pod 는 불변 객체로 취급되기 때문에 운영중에 환경 변수를 동적으로 변경할 수 없다. 환경 변수는 파드를 기동할 때에만 설정된다. 따라서, Secret 을 변경 후 파드에 반영하기 위해서는 파드를 삭제 후 재시작 하여야만 한다. Deployment 로 관리하는 경우에는 아래 커맨드로 재시작하면 된다.

$ kubectl rollout restart deployment <디플로이먼트명>

 

 

2. 볼륨 마운트 방식

볼륨 마운트 방식에서는 Secret 변경 후 최대 1분 이내에 변경사항이 파드에 자동 반영된다. 아래 매니페스트로 username 과 password 가 각각 "admin", "adminpassword" 로 설정된 시크릿을 생성 후, 이를 파드에서 볼륨 마운트한 후, 시크릿 내용을 "root", "rootpassword" 로 변경 후 1분 후에 파드의 볼륨을 확인해보자

# 시크릿 매니페스트
apiVersion: v1
kind: Secret
metadata:
  name: sample-db-auth
type: Opaque
data:
  username: YWRtaW4= # admin
  password: YWRtaW5wYXNzd29yZA== # adminpassword
# 파드 매니페스트
apiVersion: v1
kind: Pod
metadata:
  name: sample-secret-multi-volume
spec:
  containers:
    - name: secret-container
      image: nginx:1.27
      volumeMounts:
        - name: config-volume
          mountPath: /config
  volumes:
    - name: config-volume
      secret:
        secretName: sample-db-auth
# 파드 내 볼륨 확인
$ kubectl exec -it sample-secret-multi-volume -- paste /config/password /config/username
adminpassword	admin
# 시크릿 매니페스트 변경
apiVersion: v1
kind: Secret
metadata:
  name: sample-db-auth
type: Opaque
data:
  username: cm9vdA== # root
  password: cm9vdHBhc3N3b3Jk # rootpassword
# 변경된 시크릿 적용
$ kubectl apply -f sample-db-auth.yaml

# 파드에 반영되었는지 확인
$ kubectl exec -it sample-secret-multi-volume -- paste /config/username /config/password
root	rootpassword

 

3. 볼륨 갱신 메커니즘 설명

시크릿을 볼륨으로 파드에 마운트하였을 때는 자동 갱신 되는데, 이는 노드의 kubelet 이 Secret 리소스 수정을 감시하고 있기 때문이다. 감시하는 매커니즘은 아래 세가지 중 하나로 설정할 수 있다.

방식 설명
watch 이벤트 기반(실시간 감시)
ttl 캐시 만료 기반(기본값)
polling 주기적 변경여부 조회

이는 노드 kubelet 전역설정이며, 즉 해당 노드에서 동작하는 모든 파드에 일괄 적용되는 정책이다. 파드나 네임스페이스 단위에서 조정할 수는 없다. 

노드의 /var/lib/kubelet/config.yaml 파일에서 configMapAndSecretChangeDetectionStrategy 항목으로 설정하면 된다.

# watch 방식으로 변경
apiVersion: kubelet.config.k8s.io/v1beta1
...
configMapAndSecretChangeDetectionStrategy: watch
...
# ttl 방식의 캐시 유효시간 30초로 변경
apiVersion: kubelet.config.k8s.io/v1beta1
...
configMapAndSecretChangeDetectionStrategy: ttl
syncFrequency: 30s
...

설정파일 저장 후에는 kubelet 재시작이 필요하다.

 

4. 일반적으로 사용하는 방법

kubelet 의 ttl 시간을 변경하는 것은 보통 k8s 클러스터 관리자의 권한이다. 따라서 개발자의 경우에는 Secret 변경 후 rollout restart 를 하는 방식으로 운영하는게 일반적이다. 이렇게 적용하면 즉시 반영되며 예측 가능하게 된다.

kubectl apply -f <시크릿매니페스트파일명>
kubectl rollout restart deployment <디플로이먼트명>

 

블로그 이미지

망원동똑똑이

프로그래밍 지식을 자유롭게 모아두는 곳입니다.

,

시크릿은 파드의 컨테이너에서 참조하여 사용하게 된다. 환경 변수로 전달하는 방법과 볼륨으로 마운트하여 사용하는 방법이 있다.

  • 환경 변수로 전달
    • 시크릿의 특정 키 전달: spec.containers[].env[].valueForm.secretKeyRef.name/key
    • 시크릿 전체 키 전달: spec.containers[].envFrom[].secretRef.name
  • 볼륨으로 마운트
    • 시크릿의 특정 키 마운트: spec.volumes[].secret.items[].key/path
    • 시크릿 전체 키 마운트: spec.volumes[].secret.secretName

1. 환경변수로 특정 키 전달

아래 매니페스트와 같이 env[] 에 환경변수들을 이름과 값으로 하나씩 직접 지정한다. sample-db-auth 라는 시크릿에 username 키로 담긴 값을 DB_USERNAME 이라는 환경변수로 세팅하는 것이다.

apiVersion: v1
kind: Pod
metadata:
  name: sample-secret-single-env
spec:
  containers:
    - name: secret-container
      image: nginx:1.27
      env:
        - name: DB_USERNAME
          valueFrom:
            secretKeyRef:
              name: sample-db-auth
              key: username
$ kubectl apply -f sample-secret-single-env.yaml
$ kubectl exec -it sample-secret-single-env -- env | grep DB_USER
DB_USERNAME=root

 

2. 환경변수로 전체 키 전달

직접 환경변수명과 불러올 시크릿 값을 지정하지 않고 시크릿 전체를 환경변수로 그대로 등록하는 방법이다. 간단하긴 하지만, 매니페스트만 봐서는 어떤 환경변수가 설정되는지 알 수 없다는 단점이 있다.

아래 매니페스트처럼 간단히 envFrom[] 에 가져올 시크릿들의 참조를 걸어주면 된다.

apiVersion: v1
kind: Pod
metadata:
  name: sample-secret-multi-env
spec:
  containers:
    - name: secret-container
      image: nginx:1.27
      envFrom:
      - secretRef:
          name: sample-db-auth
$ kubectl apply -f sample-secret-multi-env.yaml
$ kubectl exec -it sample-secret-multi-env -- env
...
password=rootpassword
username=root
...

 

여러 시크릿들을 가져오는 경우 key 가 동일하여 충돌할 가능성이 있다. 이런 경우에는 prefix 를 붙여 충돌을 방지할 수 있다.

apiVersion: v1
kind: Pod
metadata:
  name: sample-secret-prefix-env
spec:
  containers:
    - name: secret-container
      image: nginx:1.27
      envFrom:
        - secretRef:
            name: sample-db-auth
          prefix: DB1_
        - secretRef:
            name: sample-db-auth
          prefix: DB2_
$ kubectl apply -f sample-secret-prefix-env.yaml
$ kubectl exec -it sample-secret-prefix-env -- env | egrep "^DB"
DB1_password=rootpassword
DB1_username=root
DB2_password=rootpassword
DB2_username=root

 

3. 볼륨으로 특정 키 마운트

시크릿의 정보를 컨테이너의 볼륨으로 마운트 하는 방식으로, spec.containers[].volumeMounts[] 에 마운트 할 볼륨명과 마운트 경로를 지정하고, spec.volumes[] 에 생성할 볼륨명과 값을 가져올 시크릿 및 읽을 key, 그리고 마운트 경로에 생성할 파일명(path)을 지정한다. 직접 마운트 파일명을 지정한다는 것이 특징이다.

다음과 같은 매니페스트로 생성한다.

apiVersion: v1
kind: Pod
metadata:
  name: sample-secret-single-volume
spec:
  containers:
    - name: secret-container
      image: nginx:1.27
      volumeMounts:
        - name: config-volume
          mountPath: /config
  volumes:
    - name: config-volume
      secret:
        secretName: sample-db-auth
        items:
          - key: username
            path: username.txt
$ kubectl apply -f sample-secret-single-volume.yaml
$ kubectl exec -it sample-secret-single-volume -- cat /config/username.txt
root

 

4. 볼륨으로 전체 키 마운트

시크릿의 전체 정보를 컨테이너의 볼륨으로 마운트하는 방식으로, spec.containers[].volumeMounts[] 에 마운트 할 볼륨명과 마운트 경로를 지정하고, spec.volumes[] 에 생성할 볼륨명과 가져올 시크릿을 지정한다. 마운트 경로에 생성되는 파일명은 시크릿의 key 와 동일하다. 매니페스트는 간단해지지만 매니페스트만으로는 시크릿의 어떤 key-value 가 마운트되는지 알 수 없다는 단점이 있다.

다음과 같은 매니페스트로 생성한다.

apiVersion: v1
kind: Pod
metadata:
  name: sample-secret-multi-volume
spec:
  containers:
    - name: secret-container
      image: nginx:1.27
      volumeMounts:
        - name: config-volume
          mountPath: /config
  volumes:
    - name: config-volume
      secret:
        secretName: sample-db-auth
$ kubectl apply -f sample-secret-multi-volume.yaml
$ kubectl exec -it sample-secret-multi-volume -- ls /config
password  username

 

블로그 이미지

망원동똑똑이

프로그래밍 지식을 자유롭게 모아두는 곳입니다.

,

Secret 이해에서 설명한 시크릿의 종류 중에 TLS, Docker 레지스트리, Basic, SSH 시크릿을 생성하는 방법을 알본다.

 

1. TLS

인그레스 등에서 사용하는 TLS 인증서를 시크릿으로 생성할 때 사용하는 시크릿 타입이다. 데이터 스키마로는 tls.crt 와 tls.key 를 가진다. 매니페스트에 직접 시크릿 값을 지정할 수도 있지만, 인증서 비밀키와 인증서 파일로 지정하는 것이 일반적이다.

샘플로 사용하기 위한 자체 서명 인증서를 아래 커맨드로 생성한다.

$ openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout ~/tls.key -out ~/.tls.crt -subj "/CN=sample1.example.com"

아래 커맨드로 key 파일과 인증서 파일 경로를 지정하여 tls 타입 인증서를 생성한다.

$ kubectl create secret tls --save-config tls-sample \
> --key ~/tls.key --cert ~/.tls.crt

 

2. Docker 레지스트

도커 허브 같은 도커 레지스트리의 private 저장소를 사용중이라면, 이미지를 업로드/다운로드 하기 위해 인증이 필요하다. k8s 에서는 이러한 인증 정보를 저장하기 위해 docker-registry 타입의 시크릿을 사용한다. .dockerconfigjson 데이터 스키마를 가지는 시크릿이다.

매니페스트로도 생성할 수 있지만, 형식이 특수하기 때문에 일반적으로 kubectl 로 직접 생성한다.

$ kubectl create secret docker-registry --save-config sample-registry-auth \
  --docker-server=REGISTRY_SERVER \
  --docker-username=REGISTRY_USER \
  --docker-password=REGISTRY_USER_PASSWORD \
  --docker-email=REGISTRY_USER_EMAIL

 

값으로는 자신이 사용하는 저장소와 계정정보를 넣으면 된다. 아래 커맨드로 시크릿을 조회해보면 dockerconfig json 문자열 자체가 base64 인코딩되어있다. 따라서 매니페스트에 작성하는 것 보다는 kubectl 로 작성하거나, 매니페스트로 저장하고 생성하고 싶다면 --dry-run -o yaml 하여 리다이렉트한 파일을 만들어 apply 하는 것이 좋다.

$ kubectl get secrets sample-registry-auth -o json | jq '.data'
{
  ".dockerconfigjson": "eyJhdXRocyI6eyJSRUdJU1RSWV9TRVJWRVIiOnsidXNlcm5hbWUiOiJSRUdJU1RSWV9VU0VSIiwicGFzc3dvcmQiOiJSRUdJU1RSWV9VU0VSX1BBU1NXT1JEIiwiZW1haWwiOiJSRUdJU1RSWV9VU0VSX0VNQUlMIiwiYXV0aCI6IlVrVkhTVk5VVWxsZlZWTkZVanBTUlVkSlUxUlNXVjlWVTBWU1gxQkJVMU5YVDFKRSJ9fX0="
}

$ kubectl get secrets sample-registry-auth -o json | jq -r '.data[".dockerconfigjson"]' | base64 --decode
{"auths":{"REGISTRY_SERVER":{"username":"REGISTRY_USER","password":"REGISTRY_USER_PASSWORD","email":"REGISTRY_USER_EMAIL","auth":"UkVHSVNUUllfVVNFUjpSRUdJU1RSWV9VU0VSX1BBU1NXT1JE"}}}

 

docker-registry 시크릿을 실제 이미지를 다운받는 파드에서 사용하려면, 파드 매니페스트의 spec.imagePullSecrets[] 에 시크릿을 지정해주면 된다. 파드에는 여러 컨테이너가 존재할 수 있으므로(멀티 컨테이너 파드) imagePullSecrets 도 배열로 지정한다.(컨테이너 각각에 시크릿을 설정하는 방법은 없다.)

apiVersion: v1
kind: Pod
metadata:
  name: sample-pull-secret
spec:
  containers:
    - name: secret-image-container
      image: REGISTRY_NAME/secret-image:latest
  imagePullSecrets:
    - name: sample-registry-auth

멀티 컨테이너에서 각각의 이미지 저장소가 다르다면, 아래와 같은 매커니즘으로 시크릿을 탐색한다.

  1. kubelet이 이미지 pull
  2. imagePullSecrets에 등록된 Secret들을 순서대로 모두 시도
  3. 해당 레지스트리에 매칭되는 인증 정보가 있으면 성공

또 다른 방법은, Secret 1개에 여러 레지스트리 인증정보를 집어넣는 것이다. 아래처럼 지정된 형식의 json 구조에 인증정보를 저장해 두고, Secret 생성시 .dockerconfigjson 이라는 key 로 지정하여 생성하면 된다.

// dockerconfigjson 파일 내용
{
  "auths": {
    "registry-a.example.com": {
      "auth": "dXNlcjpwYXNz"
    },
    "registry-b.example.com": {
      "auth": "dXNlcjI6cGFzczI="
    }
  }
}

 

아래 커맨드로 Secret 을 생성한다.

$ kubectl create secret --save-config sample-multi-registry-secret \
  --type=kubernetes.io/dockerconfigjson \
  --from-file=.dockerconfigjson=./dockerconfigjson

 

3. Basic

id, password 기반의 인증인 경우에 적합한 시크립 타입이다. username, password 데이터 스키마를 지정해야 한다.

kubectl 로 생성시 --type kubernetes.io/basic-auth 와 --from-literal 로 인증정보를 지정하여 생성하거나, 아래와 같은 매니페스트로 생성한다.

apiVersion: v1
kind: Secret
metadata:
  name: sample-basic-auth
type: kubernetes.io/basic-auth
data:
  username: cm9vdA== # root
  password: cm9vdHBhc3N3b3Jk # rootpassword

 

4. SSH

비밀키로 인증하는 시스템인 경우 SSH 인증용 스키마를 가진 ssh-auth 타입 시크릿을 사용한다. ssh-privatekey 라는 데이터 스키마를 사용하는 타입이다. 비밀키 파일이 크기 때문에 일반적으로 kubectl 로 --from-file 옵션으로 비밀키 파일을 지정하여 생성한다. 마찬가지로 매니페스트 파일로 생성하여 apply 하려면 --dry-run -o yaml 리다이렉션 하여 사용한다.

아래 커맨드를 이용하여 샘플 키를 생성하고 이를 기반으로 SSH 시크릿을 생성한다.

$ ssh-keygen -t rsa -b 2048 -f sample-key -C "sample"

$ kubectl create secret generic --save-config sample-ssh-auth \
> --type kubernetes.io/ssh-auth \
> --from-file=ssh-privatekey=./sample-key
secret/sample-ssh-auth created

 

블로그 이미지

망원동똑똑이

프로그래밍 지식을 자유롭게 모아두는 곳입니다.

,

Secret 리소스의 타입 중에 Opaque 타입은 일반적인 스키마리스 시크릿을 위한 타입이다. 시크릿 리소스 하나에는 여러 key-value 값들이 저장될 수 있다. 하나의 시크릿에 저장 가능한 사이즈는 1MB 까지이다.

생성하는 4가지 방법을 설명한다.

 

1. 매니페스트에서 생성

일반적인 리소스 생성 방식과 동일하게 kubectl apply -f <매니페스트파일명> 또는 kubectl create -f <매니페스트파일명> --save-config 으로 생성하는 방식이다. 아래 매니페스트는 username, password 를 시크릿으로 생성한다.

apiVersion: v1
kind: Secret
metadata:
  name: sample-db-auth
type: Opaque
data:
  username: cm9vdA== # root
  password: cm9vdHBhc3N3b3Jk # rootpassword

하지만 시크릿 값이 base64 로 되어있어야 한다는 번거로움이 있다. 일반 텍스트로 작성하려면 아래와 같이 data 대신 stringData 항목을 설정한다.

apiVersion: v1
kind: Secret
metadata:
  name: sample-db-auth-nobase64
type: Opaque
stringData:
  username: root
  password: rootpassword

아래 커맨드로 생성 후 조회해보자

$ kubectl create -f sample-db-auth-nobase64.yaml --save-config

$ kubectl get secrets sample-db-auth-nobase64 -o json | jq '.data'
{
  "password": "cm9vdHBhc3N3b3Jk",
  "username": "cm9vdA=="
}

$ kubectl get secrets sample-db-auth-nobase64 -o json | jq -r '.data.username' | base64 --decode
root

 

2. 리터럴 값을 전달하여 생성

kubectl 커맨드로 시크릿을 생성하되, --from-literal 옵션으로 직접 시크릿 key-value 쌍을 전달하는 방식이다. 아래처럼 생성한다.

$ kubectl create secret generic --save-config sample-db-auth \
  --from-literal=username=root --from-literal=password=rootpassword

 

3. envfile 기반 생성

kubectl 커맨드로 시크릿을 생성하되, --from-env-file 옵션으로 시크릿 key-value 쌍들이 정의된 파일을 지정하는 방식이다. Docker 에서 --env-file 옵션을 사용하여 컨테이너를 기동하는 경우에는 그대로 시크릿 이관이 가능하다. 아래처럼 생성한다.

# env-secret.txt 파일의 내용
username=root
password=rootpassword

# 생성 커맨드
$ kubectl create secret generic --save-config sample-db-auth \
  --from-env-file=./env-secret.txt

 

4. 파일의 내용을 value 로 생성

kubectl 커맨드로 시크릿을 생성하되, --from-file 옵션으로 시크릿 key-value 로 사용될 파일들을 지정하는 방식이다. 파일명이 key 가 되고, 파일 내용이 value 가 되기 때문에 아래 내용에 주의한다.

  • 파일명에 확장자를 붙이지 않는다.
  • 파일 내용에 개행이 붙지 않게 한다.

만약 파일명 대신 다른 key 를 사용하고자 한다면 --from-file=<사용할key>=<파일명> 형식으로 입력한다. 파일 내용에 개행이 붙지 않게 하려면 printf 나 echo -n 옵션으로 출력하여 리다이렉션 하면 된다. 아래 커맨드처럼 입력하여 생성한다.

$ printf "root" > ./username
$ printf "rootpassword" > ./password
$ kubectl create secret generic --save-config sample-db-auth \
  --from-file=./username --from-file=./password

 

블로그 이미지

망원동똑똑이

프로그래밍 지식을 자유롭게 모아두는 곳입니다.

,

1. 사용 이유

예를 들어 데이터베이스를 쿠버네티스 파드에 컨테이너로 띄운다면, 데이터베이스 사용자 아이디와 패스워드 같은 기밀 정보가 컨테이너에 주입되어야 한다. 이를 위한 몇가지 방법을 생각해볼 수 있다.(Docker 기준)

  • 이미지를 정의하는 Dockerfile 에 직접 아이디와 패스워드를 기재하는 방법
  • 이미지를 정의하는 Dockerfile 에 ARG 지시자로 수신하고, ENV 지시자로 런타임 변수로 저장하도록 작성하고 이미지 빌드시 --build-arg 로 빌드 인수로 아이디와 패스워드를 전달하는 방법
  • 컨테이너 실행시에만 -e 로 인수로 아이디와 패스워드를 전달하는 방법
  • Pod나 Deployment의 매니페스트 파일에 직접 아이디와 패스워드를 기재하는 방법

그러나, 이미지 정의 파일에 기재하거나 이미지 빌드시 인수로 넘기는 방법은 이미지 자체에 기밀 정보가 포함되기 때문에 보안상 바람직하지 않다. 이미지 레지스트리 서버에 올라가기 때문이다. 더구나 기밀 정보 내용을 변경할 때 마다 이미지를 재빌드 하여야 한다는 번거로움이 있다.(빌드시 DOCKER_BUILDKIT 을 사용하여 secret 을 이미지에 저장되지 않도록 할 수 있기는 하다.)

Pod나 Deployment 매니페스트에 기밀 정보를 기재하는 것도 해당 매니페스트를 github 같은 소스 저장소에 올리기 때문에 보안상 바람직하지 않다. 또한 해당 기밀 정보를 여러 애플리케이션에서 필요로 할 경우 애플리케이션 마다 기밀 정보를 중복되게 기재해야 하기 때문에 관리가 어려워진다.

이러한 문제점들을 해결하기 위해서 Secret 리소스를 사용할 수 있다. 기밀 정보를 별도의 리소스에 단일 저장해두고 여러 파드에서 읽어들이는 것이다.

 

2. 시크릿 리소스의 종류(타입)

종류 용도
Opaque 범용
kubernetes.io/tls TLS 인증서용
kubernetes.io/basic-auth 기본 인증용
kubernetes.io/dockerconfigjson 도커 레지스트리 인증 정보용
kubernetes.io/ssh-auth SSH 인증 정보용
kubernetes.io/service-account-token 서비스 어카운트 토큰용
bootstrap.kubernetes.io/token Bootstrap 토큰용

 

블로그 이미지

망원동똑똑이

프로그래밍 지식을 자유롭게 모아두는 곳입니다.

,

1. 정적 설정

spec.containers[].env[] 배열에 정적인 값들을 설정한다. 다음과 같은 매니페스트로 파드를 생성해본다.

apiVersion: v1
kind: Pod
metadata:
  name: sample-env
  labels:
    app: sample-app
spec:
  containers:
    - name: nginx-container
      image: nginx:1.27
      env:
        - name: MAX_CONNECTION
          value: "100"

아래 커맨드를 이용해서 컨테이너 내부에 환경 변수가 설정된 것을 확인한다.

$ kubectl exec -it sample-env -- env | grep MAX_CONNECTION
MAX_CONNECTION=100

기본적으로 컨테이너의 timezone 은 UTC 로 설정되어 있다. 아래 커맨드를 실행해보자.

$ kubectl exec -it sample-env -- date
Sun Jan  4 07:10:26 UTC 2026

 

아래처럼 정적 설정을 통해서 타임존을 Asia/Seoul 로 변경해보자.

...
      env:
        - name: MAX_CONNECTION
          value: "100"
        - name: TZ
          value: Asia/Seoul
...

단, Standalone Pod 이므로, 매니페스트 변경 후 kubectl apply 로는 변경점 적용이 되지 않는다. k8s 는 파드를 불변 객체에 가깝게 취급하기 때문에 컨테이너 환경변수를 런타임에 직접 바꾸는 것이 허용되지 않는 것이다.(허용된 필드만 런타임에 수정 적용 가능함) 따라서 파드를 삭제한 후 다시 생성한다. Deployment 로 관리하는 파드라면 Deployment 변경을 apply 하면 된다.

$ kubectl delete -f sample-env.yaml

# 매니페스트의 spec.containers[].env 변경 후 파드 재생성
$ kubectl apply -f sample-env.yaml
$ kubectl exec -it sample-env -- cat date
Sun Jan  4 16:33:23 KST 2026

 

2. 파드 정보로 설정

파드 매니페스트에서 지정한 값 이외에, 파드가 기동된 후에야 알 수 있는 값들이 있다. 이러한 추가적인 파드 정보들을 환경변수로 사용하려면, valueFrom.fieldRef.fieldPath 항목을 사용할 수 있다. fieldPath 에 들어오는 값은 kubectl get pods <파드명> -o yaml 으로 조회한 경로를 사용하면 된다.(-o json | jq 또는 -o jsonpath 으로 path 를 선택하여 보면 편하다.)

아래 예시는 파드가 실제 띄워진 노드의 이름을 K8S_NODE 라는 컨테이너 환경변수로 지정한 것이다.

apiVersion: v1
kind: Pod
metadata:
  name: sample-env-pod
  labels:
    app: sample-app
spec:
  containers:
    - name: nginx-container
      image: nginx:1.27
      env:
        - name: K8S_NODE
          valueFrom:
            fieldRef:
              fieldPath: spec.nodeName

아래 명령어를 실행하여 컨테이너 내부 환경변수를 확인한다.

$ kubectl exec -it sample-env-pod -- env | grep K8S_NODE
K8S_NODE=worker02

 

3. 컨테이너 정보로 설정

파드 정보가 아닌 파드 내의 컨테이너 정보를  환경변수로 설정하기 위해서는 valueFrom.resourceFieldRef 항목을 사용한다. 세팅 가능한 path 는 파드와 마찬가지로 kubectl get pods <파드명> -o yaml 에서 확인 가능하다.(아래 예제에서는 컨테이너에 resources 항목을 직접 지정하지 않았기 때문에 -o yaml 이나 describe 로는 조회되지는 않는다.)

아래 매니페스트를 이용하여 CPU_REQUEST 에 nginx-container 컨테이너의 CPU 리소스 최솟값을, CPU_LIMITS 에 CPU 리소스 최댓값을 환경변수로 설정해 보자.

apiVersion: v1
kind: Pod
metadata:
  name: sample-env-container
  labels:
    app: sample-app
spec:
  containers:
    - name: nginx-container
      image: nginx:1.27
      env:
        - name: CPU_REQUESTS
          valueFrom:
            resourceFieldRef:
              containerName: nginx-container
              resource: requests.cpu
        - name: CPU_LIMITS
          valueFrom:
            resourceFieldRef:
              containerName: nginx-container
              resource: limits.cpu

아래 커맨드를 이용해 컨테이너 내부 환경변수를 확인한다.

$ kubectl exec -it sample-env-container -- env | grep CPU
CPU_REQUESTS=0
CPU_LIMITS=4

 

4. 시크릿 리소스 참조

기밀정보를 환경변수로 사용하고자 할 때는 시크릿 리소스를 생성하여 파드에서는 이를 참조하게 한다. 참조 방식이기 때문에 관리가 편하다.

 

5. 컨피그맵 리소스 참조

단순 키-밸류 값이나 설정파일을 환경변수로 사용하고자 할 때는 ConfigMap 리소스를 생성하여 파드에서는 이를 참조하게 한다. 참조 방식이기 때문에 관리가 편하다.

 

5. 환경변수를 command, args 에서 사용시 유의사항

아래 매니페스트로 파드를 실행하면 "echo ${TESTENV} ${HOSTNAME}" 커맨드가 실행되어 "100 sample-env-fail" 같은 출력이 예상된다. 하지만 실제 실행해보면 "${TESTENV} ${HOSTNAME}" 가 문자열 그대로 출력됨을 알 수 있다.

apiVersion: v1
kind: Pod
metadata:
  name: sample-env-fail
  labels:
    app: sample-app
spec:
  containers:
    - name: nginx-container
      image: nginx:1.27
      command: ["echo"]
      args: ["${TESTENV}", "${HOSTNAME}"]
      env:
        - name: TESTENV
          value: "100"
$ kubectl logs sample-env-fail
${TESTENV} ${HOSTNAME}

쿠버네티스에서 파드 템플릿의 command 나 args 로 명령어를 실행할 때에는 일반적인 쉘 스크립트처럼 ${변수명} 이나 $변수명 으로 변수를 사용할 수 없다. 대신 $(변수명) 을 사용해야 한다. 또한, 파드 템플릿에 정의된 환경변수만 사용 가능하다. $(변수명) 형태로 수정된 매니페스트로 파드를 실행한 후 출력 결과를 확인해보자

$ kubectl logs sample-env-fail2
100 $(HOSTNAME)

$(TESTENV) 는 정상 출력되었지만, $(HOSTNAME) 은 문자열 그대로 출력되었다. 파드 템플릿에서 정의된 변수는 TESTENV 뿐이기 때문이다. 만약 HOSTNAME 처럼 OS 에서 참조 가능한 환경 변수를 사용하고 싶다면, 별도의 셸 스크립트로 만든 후 실행해야 한다. 아래 매니페스트로 파드를 실행해 확인해보자

apiVersion: v1
kind: Pod
metadata:
  name: sample-env-success
  labels:
    app: sample-app
spec:
  containers:
    - name: nginx-container
      image: nginx:1.27
      command: ["/bin/sh", "-c"]
      args:
        - |
          cat << 'EOF' > /endpoint.sh
          #!/bin/sh
          echo "Endpoint script started"
          echo ${TESTENV} ${HOSTNAME}
          EOF

          chmod +x /endpoint.sh
          exec /endpoint.sh
      env:
        - name: TESTENV
          value: "100"
$ kubectl logs sample-env-success
Endpoint script started
100 sample-env-success

정상적으로 ${TESTENV} 와 ${HOSTNAME} 이 모두 출력되는 것을 볼 수 있다.

시크릿 리소스를 참조하는 방식, 컨피그맵 리소스를 참조하는 방식, 파드 정보를 참조하는 방식(fieldRef), 컨테이너 정보를 참조하는 방식(resourceFieldRef) 모두 마찬가지로 $(변수명) 으로 사용해야 하며, 파드 템플릿에 정의한 환경변수만 사용 가능하다. 아래 매니페스트로 파드를 실행해 확인해보자

apiVersion: v1
kind: Pod
metadata:
  name: sample-env-fail3
  labels:
    app: sample-app
spec:
  containers:
    - name: nginx-container
      image: nginx:1.27
      command: ["echo"]
      args: ["$(K8S_NODE)", "${K8S_NODE}"]
      env:
        - name: K8S_NODE
          valueFrom:
            fieldRef:
              fieldPath: spec.nodeName
$ kubectl logs sample-env-fail3
worker01 ${K8S_NODE}

 

블로그 이미지

망원동똑똑이

프로그래밍 지식을 자유롭게 모아두는 곳입니다.

,

Ingress 는 L7 네트워크 레이어에서 작동하는 로드 밸런서이다. 인그레스는 Service 종류의 하나가 아니라, 정해진 규칙에 따라 여러 Service들로 라우팅을 해주는 독립적인 리소스이다.

 

인그레스는 인그레스 리소스만을 가리키는 말이 아니라, Ingress 리소스 + Ingress 컨트롤러 + Ingress 클래스를 포괄하는 용어이다. 각각은 아래와 같은 역할을 한다.

  • Ingress 리소스: 매니페스트에 라우팅 규칙을 등록하여 API 를 분산
  • Ingress 컨트롤러: Ingress 리소스를 기반으로 실제 로드 밸런서를 생성하며, Ingress 리소스를 감시하여 변경된 사항을 로드 밸런서에 반영
  • Ingress 클래스: Ingress 컨트롤러가 어떤 Ingress 리소스를 감시/처리할지의 기준을 명시하는 리소스

 

인그레스에는 크게 두가지 타입이 존재하는데, 하나는 클라우드 프로바이더에서 제공하는 인그레스로, k8s 클러스터 외부 로드 밸런서를 사용하는 인그레스이고, 또 하나는 k8s 클러스터 내부에 자체적으로 인그레스용 파드를 띄워서 사용하는 인그레스이다.

종류 전달순서 예시
클러스터 외부 로드 밸런서를 사용한 인그레스 클라이언트 -> 외부 L7 로드 밸런서(인그레스) -> NodePort 서비스 -> 목적지 파드 GKE 인그레스
클러스터 내부 파드를 사용한 인그레스 클라이언트 -> 내부 L4 LoadBalancer 서비스 -> 인그레스 파드(L7) -> 목적지 파드 Nginx 인그레스

 

Public Cloud Provider Ingress

 

 

Nginx Pod Ingress

 

우리는 ingress-nginx 를 사용하는 방법을 알아본다.

 

1. nginx 인그레스 컨트롤러 설치

https://github.com/kubernetes/ingress-nginx/blob/main/docs/deploy/index.md#bare-metal-clusters 을 참고하여 설치

$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.14.1/deploy/static/provider/baremetal/deploy.yaml

 

단, 기본적으로 NodePort 서비스가 생성되는데, 자동으로 30000-32767 사이의 포트를 할당받고, Node IP 의 해당 포트로 호출하여야 한다. 아래 커맨드로 생성된 NodePort 서비스를 확인한다.

$ kubectl -n ingress-nginx get svc
NAME                                 TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                      AGE
ingress-nginx-controller             NodePort    10.110.81.89     <none>        80:32321/TCP,443:32165/TCP   23s
ingress-nginx-controller-admission   ClusterIP   10.110.134.192   <none>        443/TCP                      23s

아래 커맨드로 Ingress 디플로이먼트, 레플리카셋, 파드를 확인한다.

$ kubectl -n ingress-nginx get deploy
NAME                       READY   UP-TO-DATE   AVAILABLE   AGE
ingress-nginx-controller   1/1     1            1           2m51s

$ kubectl -n ingress-nginx get rs
NAME                                  DESIRED   CURRENT   READY   AGE
ingress-nginx-controller-68f58fbbcc   1         1         1       3m17s

$ kubectl -n ingress-nginx get pods
NAME                                        READY   STATUS    RESTARTS   AGE
ingress-nginx-controller-68f58fbbcc-2tqhj   1/1     Running   0          4m27s

 

2. LoadBalancer 를 사용하기 위해 metallb 설치

https://github.com/kubernetes/ingress-nginx/blob/main/docs/deploy/baremetal.md#a-pure-software-solution-metallb 을 참고하여 metallb를 설치한다.

# 1) kube-proxy IPVS 모드를 사용하는 경우 strick ARP 모드 활성화
$ kubectl edit configmap -n kube-system kube-proxy
# mode: "ipvs" 로 수정
# ipvs.strictARP: true 로 수정
# 아래 커맨드로 바로 수정도 가능
# see what changes would be made, returns nonzero returncode if different
$ kubectl get configmap kube-proxy -n kube-system -o yaml | \
  sed -e "s/strictARP: false/strictARP: true/" | \
  kubectl diff -f - -n kube-system

# actually apply the changes, returns nonzero returncode on errors only
$ kubectl get configmap kube-proxy -n kube-system -o yaml | \
  sed -e "s/strictARP: false/strictARP: true/" | \
  kubectl apply -f - -n kube-system

# 2) manifest 를 사용하여 metallb 설치
$ kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.15.3/config/manifests/metallb-native.yaml

 

3. IPAddressPool, L2Advertisement 리소스 생성

아래 매니페스트로 LoadBalancer 생성에 필요한 IPAddressPool, L2Advertisement 를 생성한다.

---
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: default
  namespace: metallb-system
spec:
  addresses:
  - 203.0.113.10-203.0.113.15
  autoAssign: true
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: default
  namespace: metallb-system
spec:
  ipAddressPools:
  - default

 

단, IPAddressPool 의 spec.addresses 의 IP 대역은 실제 사용하지 않고있고, 앞으로도 사용할 가능성이 없는 대역으로 지정해야 한다.

아래 커맨드를 참고하여 사용하지 않는 IP 를 확인한다.

# 1) 현재 노드가 속한 네트워크 대역 확인
$ ip -4 addr
...
inet 172.30.1.3/24
...

# 2) 현재 네트워크에서 이미 사용중인 IP 확인
$ sudo apt install -y arp-scan
$ sudo arp-scan 172.30.1.0/24

# 3) DHCP가 사용하는 범위 확인
# 공유기/라우터 관리 페이지 DHCP 설정 대역 확인

 

 

3. LoadBalancer 생성

아래 커맨드를 실행해서 기본 서비스 타입인 NodePort 가 아닌 LoadBalancer 타입으로 서비스를 띄워줘야 함.(공식 제공되는 ingress-nginx-controller 초기 yaml 에는 NodePort 로 지정되어있기 때문)

$ kubectl -n ingress-nginx patch svc ingress-nginx-controller -p '{"spec":{"type":"LoadBalancer"}}'

 

4. Backend 파드와 서비스 생성

아래 매니페스트로 파드와 서비스를 생성한다.

---
apiVersion: v1
kind: Service
metadata:
  name: sample-ingress-svc-1
spec:
  type: NodePort
  ports:
    - name: "http-port"
      protocol: "TCP"
      port: 8888
      targetPort: 80
  selector:
    ingress-app: sample1
---
apiVersion: v1
kind: Pod
metadata:
  name: sample-ingress-apps-1
  labels:
    ingress-app: sample1
spec:
  containers:
    - name: nginx-container
      image: nginx:1.27
      ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: sample-ingress-svc-2
spec:
  type: NodePort
  ports:
    - name: "http-port"
      protocol: "TCP"
      port: 8888
      targetPort: 80
  selector:
    ingress-app: sample2
---
apiVersion: v1
kind: Pod
metadata:
  name: sample-ingress-apps-2
  labels:
    ingress-app: sample2
spec:
  containers:
    - name: nginx-container
      image: nginx:1.27
      ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: sample-ingress-default
spec:
  type: NodePort
  ports:
    - name: "http-port"
      protocol: "TCP"
      port: 8888
      targetPort: 80
  selector:
    ingress-app: default
---
apiVersion: v1
kind: Pod
metadata:
  name: sample-ingress-default
  labels:
    ingress-app: default
spec:
  containers:
    - name: nginx-container
      image: nginx:1.27
      ports:
        - containerPort: 80

 

5.  ingress 리소스 생성

아래 매니페스트로 인그레스 리소스를 생성한다. 리소스가 생성되면 인그레스 컨트롤러가 자동으로 읽어 인그레스 디플로이먼트 파드에서 라우팅이 된다.

Ingress 는 기본적으로 http 요청을 https 로 리다이렉트 해주는데 nginx.ingress.kubernetes.io/ssl-redirect: "false" 어노테이션을 주어 비활성화 한다. spec.ingressClassName 항목은 ingressClass 를 지정하는 항목인데, 기본적으로 생성되는 ingressClass인 nginx 를 지정하면 된다. spec.defaultBackend 에 지정한 서비스는 매칭되는 경로가 없을 때 라우팅되는 서비스이다.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: sample-ingress-by-nginx
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "false"
spec:
  ingressClassName: nginx
  rules:
    - host: sample.example.com
      http:
        paths:
          - path: /path1/
            pathType: Prefix
            backend:
              service:
                name: sample-ingress-svc-1
                port:
                  number: 8888
          - path: /path2/
            pathType: Prefix
            backend:
              service:
                name: sample-ingress-svc-2
                port:
                  number: 8888
  defaultBackend:
    service:
      name: sample-ingress-default
      port:
        number: 8888
  tls:
    - hosts:
        - sample.example.com
      secretName: tls-sample

 

spec.rules[].http.paths[].pathType 항목은 요청 URL 의 path 를 어떤 방식으로 매칭할 것인지를 정의한다. Ingress Controller 는 이 설정을 기반으로 어느 backend service 로 라우팅할지 결정한다.

값별 설명은 아래와 같다.

pathType 매칭 방식 정밀도 실무 사용 특이사항
Exact 정확히 일치 가장 엄격 드물게 사용
health check 엔드포인트 등 특수한 경우
trailing slash 도 엄격히 비교
Prefix URL prefix 일반적 권장 "/" 를 구분자로 path segment 를 분리하여 전방 일치 판단.
즉, "/api" 인 경우 "/apis" 는 매칭되지 않음.
여러 prefix 가 중복된 경로를 가진 경우, path segment 가 긴 path 일수록 우선순위 높음
ImplementationSpecific 컨트롤러에 따라 다름 불명확 비권장 컨트롤러 교체시 동작 변경 위험

 

6. 확인

# 각 노드의 IP 확인
$ kubectl get nodes -o wide
NAME       STATUS   ROLES           AGE    VERSION    INTERNAL-IP    EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION     CONTAINER-RUNTIME
master01   Ready    control-plane   172m   v1.31.3    192.168.64.5   <none>        Ubuntu 24.04.3 LTS   6.8.0-90-generic   containerd://1.7.28
worker01   Ready    <none>          144m   v1.31.14   192.168.64.6   <none>        Ubuntu 24.04.3 LTS   6.8.0-90-generic   containerd://1.7.28
worker02   Ready    <none>          136m   v1.31.14   192.168.64.7   <none>        Ubuntu 24.04.3 LTS   6.8.0-90-generic   containerd://1.7.28

# ingress 컨트롤러로 접근할 수 있는 IP 확인(worker02 의 IP 가 확인되는데, 이는 진입점이 worker02 에 떠있는 NodePort 서비스가 된다는 소리임)
$ kubectl get ingresses
NAME                      CLASS   HOSTS                ADDRESS        PORTS     AGE
sample-ingress-by-nginx   nginx   sample.example.com   192.168.64.7   80, 443   3m41s

# (NodePort 로 사용하는 경우)NodePort 서비스이기 때문에 NodePort 의 Port 를 확인하여야 함
$ kubectl -n ingress-nginx get services
NAME                                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
ingress-nginx-controller             NodePort    10.108.27.225   <none>        80:30329/TCP,443:32630/TCP   115m
ingress-nginx-controller-admission   ClusterIP   10.97.149.209   <none>        443/TCP                      115m

# LoadBalancer 서비스로 바꾼 후에는 EXTERNAL-IP 로 바로 접속하여야 함
$ kubectl -n ingress-nginx get svc ingress-nginx-controller
NAME                       TYPE           CLUSTER-IP      EXTERNAL-IP    PORT(S)                      AGE
ingress-nginx-controller   LoadBalancer   10.108.27.225   203.0.113.10   80:30329/TCP,443:32630/TCP   3h59m

# LoadBalancer IP를 변수로 저장
INGRESS_LB_IP=`kubectl -n ingress-nginx get svc ingress-nginx-controller -o jsonpath='{.status.loadBalancer.ingress[0].ip}'`

# 경로별 라우팅 확인
curl http://${INGRESS_LB_IP}/path1/ -H "Host: sample.example.com"
curl http://${INGRESS_LB_IP}/path2/ -H "Host: sample.example.com"
curl http://${INGRESS_LB_IP}/ -H "Host: sample.example.com"

 

IngressClass 란?

Ingress Controller 가 Ingress 리소스를 식별하는 기준이 된다. 아래와 같은 매니페스트로 생성한다.

apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
  name: nginx-external
spec:
  controller: example.com/ingress-nginx-external

 

항목명 의미
metadata.name 인그레스 클래스명. 인스레스 리소스에 spec.ingressClassName 을 지정하고, 인그레스 컨트롤러 파드에서 --ingress-class 옵션으로 추가하여 컨트롤러(파드)가 어떤 인그레스 리소스를 감시/처리할지 판단하는 기준이 됨
spec.controller 어떤 인그레스 컨트롤러가 사용할지 명시하는 고유 문자열

 

이렇게 생성한 IngressClass명을 Ingress 리소스의 spec.ingressClassName 에 지정해주고, Ingress Controller 에 어떤 IngressClass 를 감시/처리 실행 옵션으로 아래와 같이 지정한다. Ingress Controller 는 보통 Deployment 로 관리되는 Pod 이므로, Pod Template 의 spec.containers[].args 에 아래와 같이 지정하면 된다.

# ingress-nginx 기준 예시
...
args:
  - /nginx-ingress-controller
  - --controller-class=example.com/ingress-nginx-external
  - --ingress-class=nginx-external
  - --watch-ingress-without-class=false

 

옵션의 의미

옵션명 의미
/nginx-ingress-controller 컨테이너 내부 실행 바이너리
--controller-class IngressClass 리소스의 spec.controller 와 매칭
--ingress-class Ingress 리소스의 spec.ingressClassName 와 매칭
--watch-ingress-without-class=false class 없는 ingress 무시(운영 권장)

 

 

 

 

블로그 이미지

망원동똑똑이

프로그래밍 지식을 자유롭게 모아두는 곳입니다.

,

kubernetes 를 핸즈온으로 공부하려면 로컬 머신에서 멀티 노드 클러스터를 구축하는게 가장 편리하다는 판단을 했다. 그래서 여러 가이드를 찾아보았다. 처음에는 multipass 라는 가상화 도구를 사용해서 시도해보고, 그 다음에는 kind 라는 kubernetes 를 docker 컨테이너로 올리는 도구도 사용해봤는데, 내공이 부족하여 실패하였다. 결국 docker desktop 에서 간단히 활성화 할 수 있는 kind 로의 프로비저닝 방식을 사용하여 구축에 성공했다.

하지만, Ingress 를 실습할 차례가 왔고, kind 방식으로 구축한 클러스터는 네트워크 구성이 실제 머신을 노드로 사용하는 클러스터와 달라서 실습이 불가능함을 깨달았다. 그래서, 실제 완전한 리눅스 OS와 동일하게 구성하기 위해 UTM 가상화 도구를 사용해 멀티 노드 클러스터를 구축했고, 그 과정을 기술한다.

 

1. UTM 설치

UTM 이라는 가상화 도구를 m1 맥북에 설치한다.

 

2. 가상머신 생성

가상머신 3대를 Ubuntu 24.04.3 LTS OS를 설치하며 생성한다.

1대는 control plane 용이고, 나머지 2대는 worker node 용이다.

시스템 자원은 본인이 사용하는 컴퓨팅 리소스에 맞게 할당한다.

 

3. 클립보드 붙여넣기 설정

개발 편의성을 위해 호스트 머신과 각 게스트 머신간 복붙을 가능하게 하여야 한다.

CLI 만 사용중이라면 아래 두가지 방법이 있다.(spice-vdagent 는 GUI 용 클립보드 공유 에이전트이므로 상관하지 않는다.)

  1) 리눅스 터미널에 openssh 를 설치하여 맥북에서 직접 ssh 접속

  2) 가상머신의 display 장치를 삭제하고 직렬 포트 장치로 사용

직렬 포트 장치가 간단하지만, CLI 가 이상하게 표시되는 현상이 종종 있으므로, ssh 접속하는 방법이 사용성이 더 낫다.

 

4. hostname 설정(모든 노드)

아래 커맨드를 통해서 각 노드에 hostname 을 지정해준다.

$ sudo hostnamectl set-hostname <새 호스트명>

 

아래 커맨드를 통해서 정상적으로 hostname 이 설정되었는지 확인한다.

$ cat /etc/hostname
# 또는
$ hostname
# 또는
$ hostnamectl

 

5. /etc/hosts 수정(모든 노드)

각 노드에서 아래 명령어를 실행하여 enp0s1 장치의 inet 에 기재된 IP 를 확인한다.

$ sudo ip addr

 

각 노드의 /etc/hosts 파일에 자신의 IP 를 제외한 나머지 노드의 IP 를 hostname 과 매핑해준다.

# master01 노드의 /etc/hosts 예시
...
127.0.1.1 master01
192.168.64.6 worker01
192.168.64.7 worker02
...

 

6. swapoff 및 영구 swapoff 적용(모든 노드)

아래 커맨드를 사용하여 각 노드에서 swapoff 를 적용한다.

$ sudo swapoff -a

 

아래 커맨드를 사용해 /etc/fstab 파일을 수정하여 부팅시마다 swapoff 가 자동 적용되도록 설정한다.(swap 설정을 주석처리하여 영구 비활성화 하는 행위)

$ sudo sed -i '/swap/s/^\(.*\)$/#\1/g' /etc/fstab

 

7. 커널 네트워크에 브릿지 트래픽 허용(모든 노드)

아래 커맨드를 실행하여 브릿지 네트워크로부터 들어오는 트래픽을 허용한다.

$ cat <<'EOF' | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables=1
net.bridge.bridge-nf-call-ip6tables=1
net.ipv4.ip_forward=1
EOF

# 커널 파라미터 설정 적용
$ sudo sysctl --system

 

8. containerd 설치(모든 노드)

아래 커맨드를 순차적으로 실행하여 각 노드에 containerd 를 설치한다.

# 1) 필요한 의존성 패키지 설치
$ sudo apt update
$ sudo apt install -y ca-certificates curl gnupg lsb-release

# 2) Docker의 APT repo 대신 Ubuntu 기본 repo 에서 containerd 설치
$ sudo apt install -y containerd

# 3) 기본 config 생성
$ sudo mkdir -p /etc/containerd
$ sudo containerd config default | sudo tee /etc/containerd/config.toml

# 4) 시스템에 맞춰 cgroup 설정(기본적으로 systemd cgroup 사용 권장)
# /etc/containerd/config.toml 에서 아래를 확인/적용:
# SystemdCgroup = true
# 아래 커맨드로 바로 수정 가능
$ sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml

# 5) pause:3.10 버전으로 수정(선택)
# (이건 kubernetes 1.31.3 버전과의 호환성을 위함이며, 사용하는 kubernetes 버전의 호환성을 확인하여 맞춰주시길 바랍니다.)
$ sudo sed -i 's|sandbox_image = "registry.k8s.io/pause:3.8"|sandbox_image = "registry.k8s.io/pause:3.10"|' \
  /etc/containerd/config.toml

# 6) containerd 부팅시 자동실행 설정(enable) 및 재시작(restart)
$ sudo systemctl enable containerd && sudo systemctl restart containerd

 

9. kubeadm, kubelet, kubectl 설치(모든 노드)

아래 커맨드를 순차적으로 실행하여 각 노드에 kubeadm, kubelet, kubectl 를 설치한다.

# 1) 필요한 의존성 패키지 설치
$ sudo apt-get update
$ sudo apt-get install -y apt-transport-https ca-certificates curl

# 2) APT 저장소의 서명 키(GPG key)를 보관할 디렉터리를 생성
$ sudo mkdir -p /etc/apt/keyrings

# 3) Kubernetes APT 저장소의 GPG 서명 키 다운로드 및 변환하여 저장(v1.31)
$ curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.31/deb/Release.key | \
  sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg

# 4) Kubernetes APT 저장소 등록 및 업데이트
$ echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] \
  https://pkgs.k8s.io/core:/stable:/v1.31/deb/ /" | \
  sudo tee /etc/apt/sources.list.d/kubernetes.list
$ sudo apt-get update

# 5) 설치 (버전 고정 원하면 =<버전> 지정)
$ sudo apt-get install -y \
  kubelet=1.31.3-1.1 \
  kubeadm=1.31.3-1.1 \
  kubectl=1.31.3-1.1

# 6) 자동 업그레이드 되지 않도록 버전 고정
$ sudo apt-mark hold kubelet kubeadm kubectl

# 7) kubelet 서비스 부팅시 자동실행 설정 및 재시작
$ sudo systemctl enable kubelet && sudo systemctl restart kubelet

 

10. VM에 브릿지 네트워크 설정 및 브릿지 IP 고정(모든 노드)

UTM 에서 네트워크 장치 추가 > 브릿지 네트워크 > 실제 접속중인 네트워크 인터페이스 선택 하고 저장한다.

노드 접속 후 아래 커맨드로 게이트웨이 주소를 확인한다.

$ ip route
# 출력 예시
default via 192.168.0.1 dev enp0s2 proto dhcp src 192.168.0.36

 

default via 뒤의 IP 가 게이트웨이의 주소이므로 메모한다.(enp0s2 인터페이스의 게이트웨이)

아래 커맨드로 DNS 주소를 확인한다.

$ resolvectl status
# 출력 예시
Link 2 (enp0s2)
    Current DNS Server: 192.168.0.1
    DNS Servers: 192.168.0.1 168.126.11.1 168.126.11.2

 

DNS Servers 목록이 DNS 주소이므로 메모한다.

/etc/netplan/*.yaml 파일에 enp0s2 설정을 추가하고 게이트웨이 주소와 DNS 주소를 아래 내용처럼 적용한다.

# /etc/netplan/*.yaml 파일을 열어서 아래처럼 enp0s1, enp0s2 을 추가/수정
network:
  version: 2
  renderer: networkd
  ethernets:
    enp0s1:
      dhcp4: yes
      optional: true
    enp0s2:
      dhcp4: no
      addresses:
        - 192.168.0.36/24
      routes:
        - to: default
          via: 192.168.0.1
      nameservers:
        addresses:
          - 192.168.0.1
          - 168.126.11.1
          - 168.126.11.2

$ sudo netplan apply

 

아래 커맨드를 실행하여 enp0s2 장치에 inet 이 잡혔는지 확인한다.

$ sudo ip addr

 

만약 enp0s2 가 아직 DOWN 상태라면 아래 커맨드를 실행한다.

$ sudo ip link set enp0s2 up

 

11. 각 노드에 필요한 패키지들 설치(모든 노드)

아래 커맨드를 실행하여 각 노드에 추가 패키지를 설치한다.(클러스터 구성 및 운영에 필요)

# k8s 구성 및 운영에 필요한 패키지들 설치
$ sudo apt-get update
$ sudo apt-get install -y \
  conntrack \
  ipset \
  iptables \
  ebtables \
  ethtool \
  socat \
  ipvsadm

 

12. control-plane 초기화(master 노드)

master 노드에서 아래 커맨드를 순차적으로 실행하여 kubernetes control plane 초기화를 진행한다.

# 초기화 실행. <MASTER_NODE_BRIDGE_IP> 에는 master 노드의 브릿지 네트워크 ip를 넣는다.
# pod-network-cidr 는 고정이다.
$ sudo kubeadm init --kubernetes-version=1.31.3 \
  --pod-network-cidr=10.244.0.0/16 \
  --apiserver-advertise-address=<MASTER_NODE_BRIDGE_IP>

# 성공 후 출력되는 메시지를 따라 진행하면 된다.(아래는 예시)
To start using your cluster, you need to run the following as a regular user:

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

Alternatively, if you are the root user, you can run:

  export KUBECONFIG=/etc/kubernetes/admin.conf

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

# 위 링크를 따라 들어가면 나오는 flannel CNI 파드 네트워크 구성 예시
$ kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml

Then you can join any number of worker nodes by running the following on each as root:

# 아래 join 커맨드를 복사해놓고, 각 worker node 에서 root 권한으로 실행해줍니다.
kubeadm join...

# 가이드에는 없는데, node join 후 control plane 에서 containerd 를 재시작 해줘야 한다.
$ sudo systemctl restart containerd

 

만약 kubeadm init 중간에 실패하거나 다시 init 하려면 아래 커맨드로 reset 실행 후 다시 init 진행한다.

$ sudo kubeadm reset -f
$ sudo systemctl stop kubelet
$ sudo iptables -F && sudo iptables -t nat -F && sudo iptables -t mangle -F && sudo iptables -X
$ sudo rm -rf /etc/cni/net.d & \
  sudo ipvsadm --clear & \
  sudo rm -rf $HOME/.kube/config & \
  sudo rm -rf $HOME/.kube/cache

 

만약 flannel CNI 가 제대로 생성되지 않아 재구성해야 한다면 아래 커맨드로 삭제를 먼저 진행해야 한다.

# flannel CNI 파드 네트워크 제거 예시(CNI 파드 네트워크 구성중 문제가 발생하여 제거하고 다시 설치해야 할 때)
$ kubectl delete -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml --ignore-not-found=true

 

만약 flannel pod(DaemonSet)가 생성되지 않는다면 모든 노드(master/worker)에서 아래 순서대로 실행한다.

# 모듈 로드
$ sudo modprobe br_netfilter
$ sudo modprobe overlay

# 부팅시 유지
$ cat <<'EOF' | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF

# 브릿지 네트워크 트래픽 허용(이미 위에서 적용했으나, 재확인해보자)
$ cat <<'EOF' | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables=1
net.bridge.bridge-nf-call-ip6tables=1
net.ipv4.ip_forward=1
EOF
$ sudo sysctl --system

 

master 노드에서 아래 명령어로 flannel 재시작 후 파드를 확인한다.

# flannel 재시작
$ kubectl -n kube-flannel rollout restart ds/kube-flannel-ds

# 정상 확인
$ kubectl -n kube-flannel get pods -o wide
$ kubectl -n kube-system get pods

 

만약 worker node 에서 클러스터 join 시에 아래와 같은 에러가 나타난다면

error execution phase preflight: [preflight] Some fatal errors occurred:
        [ERROR FileAvailable--etc-kubernetes-kubelet.conf]: /etc/kubernetes/kubelet.conf already exists
        [ERROR Port-10250]: Port 10250 is in use
        [ERROR FileAvailable--etc-kubernetes-pki-ca.crt]: /etc/kubernetes/pki/ca.crt already exists

 

이미 join 했던 노드라서 클러스터 정보와 kubelet 이(10250 포트를 점유하며) 남아있기 때문이다.

해당 노드에서 아래 커맨드를 순서대로 실행하여 clean reset 한 뒤 다시 join 해야 한다.

# 1) 노드의 kubeadm 리셋
$ sudo kubeadm reset -f

# 2) CNI/네트워크 잔여물 정리
$ sudo rm -rf /etc/cni/net.d & \
  sudo ipvsadm --clear & \
  sudo rm -rf $HOME/.kube/config & \
  sudo rm -rf /var/lib/cni & \
  sudo ip link delete cni0 2>/dev/null || true & \
  sudo ip link delete flannel.1 2>/dev/null || true & \
  sudo ip link delete kube-ipvs0 2>/dev/null || true

# 3) kubelet 정리 (10250 점유 해소)
$ sudo systemctl stop kubelet && \
  sudo rm -rf /etc/kubernetes & \
  sudo rm -rf /var/lib/kubelet && \
  sudo systemctl start kubelet

# 4) containerd 재시작
$ sudo systemctl restart containerd

 

13. host 머신에서 가상머신으로 공유 폴더 설정하기(선택)

UTM 에서 가상머신 편집 > 공유에서 아래를 설정한다.

  • 디렉터리 공유 모드: VirtFS
  • 경로: 공유폴더 지정

가상머신 부팅 및 로그인 후 아래 커맨드를 실행한다.

$ sudo mkdir /media/<공유폴더이름>
$ sudo mount -t 9p -o trans=virtio share /media/<공유폴더이름> -oversion=9p2000.L
$ sudo vi /etc/fstab
# 최하단에 아래 내용 추가하고 저장
share /media/<공유폴더이름> 9p trans=virtio,version=9p2000.L,rw,_netdev,nofail 0 0

 

이제 가상머신을 재실행하면 아래 경로에서 공유폴더를 확인 가능하다.

  • /media/<공유폴더이름>

참고로, 호스트 머신에서 공유폴터 내부에서 공유폴더 외부로 심볼릭 링크 된 파일은 가상머신에서 내용을 볼 수 없다.

블로그 이미지

망원동똑똑이

프로그래밍 지식을 자유롭게 모아두는 곳입니다.

,