no image
[Kubernetes] 쿠버네티스 API와 사용방법 (2)
아래 포스팅과 내용이 이어집니다! [Kubernetes] 쿠버네티스 API와 사용방법 (1) 쿠버네티스 API 서버와 통신하기 이전 Downward API와 메타데이터 포스팅에서 DwonwardAPI가 어떻게 특정 파드와 컨테이너의 메타데이터를 그 안에서 실행되는 프로세스를 쉽게 전달하는지 살펴봤습니 easyitwanner.tistory.com 앰배서더 컨테이너를 이용한 API 서버 통신 간소화 HTTPS, 인증서, 인증 토큰을 다루는 일은 때때로 너무 복잡해 보일 때가 있습니다. 종종 서버 인증서의 유효성 검사를 비활성화하는 경우를 많이 있습니다. 실제로 이러한 사유로 공겨을 당해 보안 이슈가 공개 되는 경우도 있었구요. 다행히 보안을 유지하면서 통신을 훨씬 간단하게 만들 수 있는 방법이 있습니다. 바..
2023.05.28
[Kubernetes] 쿠버네티스 API와 사용방법 (1)
쿠버네티스 API 서버와 통신하기 이전 Downward API와 메타데이터 포스팅에서 DwonwardAPI가 어떻게 특정 파드와 컨테이너의 메타데이터를 그 안에서 실행되는 프로세스를 쉽게 전달하는지 살펴봤습니다. Downward API는 단지 파드 자체의 메타데이터와 모든 파드의 데이터 중 일부만 노출합니다. 그러나 때때로 애플리케이션에서 클러스터에 정 의된 다른 파드나 리소스에 관한 더 많은 정보가 필요할 수도 있습니다. 이 경우 Downward API는 도움이 되지 않습니다. 서비스와 파드에 관한 정보는 서비스 관련 환경변수나 DNS로 얻을 수 있습니다. 그러나 애플리케이션이 다른 리소스의 정보가 필요하거나 가능한 한 최신 정보에 접근해야 하는 경우 API 서버와 직접 통신해야 합니다. 파드 내의 애..
2023.05.27
[Kubernetes] Downward API와 메타데이터(2)
downwardAPI 볼륨에 파일로 메타데이터 전달 환경변수 대신 파일로 메타데이터를 노출하려는 경우 downwardAPI 볼륨을 정의해 컨테이너에 마운트 할 수 있습니다. 환경변수로 파드의 레이블이나 어노테이션을 노출할 수 없기 때문에 downwardAPI 볼륨을 사용해야 합니다. 이유는 나중에 추가로 다뤄보겠습니다. 환경변수와 마찬가지로 메타데이터를 프로세스에 노출시키려면 각 메타데이터 필드를 명시적으로 지정해야 합니다. 다음 예제에서 환경변수 대신 볼륨을 사용하도록 이전 예제를 수정하는 방법을 살펴보겠습니다. 예제. 1) downwardAPI 볼륨을 갖는 파드: downward-api-volume.yaml apiVersion: v1 kind: Pod metadata: name: downward la..
2023.05.27
[Kubernetes] Downward API와 메타데이터(1)
Downward API로 메타데이터 전달 오래간만에 다시 쿠버네티스로 돌아왔습니다. 이번 포스팅에서는 메타데이터를 전달하는 방법에 대해 다뤄보도록 하겠습니다. 이전에 저희는 환경변수 또는 컨피그맵과 시크릿 볼륨으로 설정 데이터를 애플리케이션에 전 달하는 방법을 살펴봤습니다. 이는 사용자가 데이터를 직접 설정하거나 파드가 노드에 스케줄링돼 실행되기 이전에 이미 알고 있는 데이터에는 적합합니다. 그러나 파드의 IP, 호스트 노드 이름 또는 파드 자체의 이름(파드의 이름이 생성되는 경우. 예를 들어 레플리카셋이나 그와 유사 한 컨트롤러에 의해 파드가 생성되는 경우)과 같이 실행 시점까지 알려지지 않은 데이터의 경우나 파드의 레이블이나 어노테이션과 같이 어딘가에 이미 설정된 데이터일 경우에 동일한 정보를 여러 ..
2023.05.27
[Kubernetes Secrets] 쿠버네티스 시크릿(Secret)이란?
목차 시크릿 기본 토큰 시크릿 시크릿 생성 컨피그맵과 시크릿 비교 stringData 필드 파드에서 시크릿 항목 읽기 파드에서 시크릿 사용 HTTPS를 활성화하도록 fortune-config 컨피그맵 수정 fortune-https 시크릿을 파드에 마운트 Nginx가 시크릿의 인증서와 키를 사용하는지 테스트 시크릿 볼륨을 메모리에 저장하는 이유 환경변수로 시크릿 항목 노출 이미지를 가져올 때 사용하는 시크릿 도커 허브에서 프라이빗 이미지 사용 도커 레지스트리 인증을 위한 시크릿 생성 파드 정의에서 도커 레지스트리 시크릿 사용 시크릿 지금까지 컨테이너에 전달한 정보는 보안을 유지할 필요가 없는 일반적이고 민감하지 않은 데이터였습니다. 그러나 설정 안에는 보안이 유지돼야 하는 자격증명과 개인 암호화 키와 같은..
2023.04.17
[Kubernetes Configmap] 컨피그맵이란? (2)
목차 컨피그맵 볼륨을 사용해 컨피그맵 항목을 파일로 노출 컨피그맵 생성 볼륨 안에 있는 컨피그맵 항목 사용 Nginx 서버가 마운트한 설정 파일을 사용하는지 확인 마운트된 컨피그맵 볼륨 내용 살펴보기 볼륨에 특정 컨피그맵 항목 노출 디렉터리를 마운트할 때 디렉터리의 기존 파일을 숨기는 것 이해 디렉터리 안에 다른 파일을 숨기지 않고 개별 컨피그맵 항목을 파일로 마운트 컨피그맵 볼륨 안에 있는 파일 권한 설정 애플리케이션 재시작 없이 애플리케이션 설정 업데이트 컨피그맵 편집 설정을 다시 로드하기 위해 Nginx에 신호 전달 파일이 한꺼번에 업데이트되는 원리 주의사항 컨테이너 불변성을 우회해도 될까? 컨피그맵 볼륨을 사용해 컨피그맵 항목을 파일로 노출 환경변수 또는 명령줄 인자로 설정 옵션을 전달하는 것은 ..
2023.04.16
반응형

아래 포스팅과 내용이 이어집니다!

 

[Kubernetes] 쿠버네티스 API와 사용방법 (1)

쿠버네티스 API 서버와 통신하기 이전 Downward API와 메타데이터 포스팅에서 DwonwardAPI가 어떻게 특정 파드와 컨테이너의 메타데이터를 그 안에서 실행되는 프로세스를 쉽게 전달하는지 살펴봤습니

easyitwanner.tistory.com

 

앰배서더 컨테이너를 이용한 API 서버 통신 간소화

HTTPS, 인증서, 인증 토큰을 다루는 일은 때때로 너무 복잡해 보일 때가 있습니다. 종종 서버 인증서의 유효성 검사를 비활성화하는 경우를 많이 있습니다. 실제로 이러한 사유로 공겨을 당해 보안 이슈가 공개 되는 경우도 있었구요. 다행히 보안을 유지하면서 통신을 훨씬 간단하게 만들 수 있는 방법이 있습니다.

 

바로 전 포스팅에서 kubectl proxy 명령을 다루었습니다. API 서버에 좀 더 쉽게 액세스할 수 있도록 로컬 컴퓨터에서 명령을 실행했습니다. API 서버로 직접 요청을 보내는 대신 프록시로 요청을 보내 인증, 암호화 및 서버 검증을 처리하게 합니다. 파드 내에서도 동일한 방법을 사용할 수 있습니다.

 

앰배서더 컨테이너 패턴 소개

API 서버를 쿼리해야 하는 애플리케이션이 있다고 가정하고 이전에 proxy를 다루었던 것처럼 API 서버와 직접 통신하는 대신 메인 컨테이너 옆의 앰배서더 컨테이너에서 kubectl proxy를 실행하고 이를 통해 API 서버와 통신할 수 있습니다.

 

API 서버와 직접 통신하는 대신 메인 컨테이너의 애플리케이션은 HTTPS 대신 HTTP 로 앰배서더에 연결하고 앰배서더 프록시가 API 서버에 대한 HTTPS 연결을 처리하도록 해 보안을 투명하게 관리할 수 있습니다. 시크릿 볼륨에 있는 default-token 파일을 사용해 이를 수행할 수 있습니다. 파드의 모든 컨테이너는 동일한 루프백 네트워크 인터페이스를 공유하므로 애플리케이션은 localhost의 포트로 프록시에 액세스할 수 있습니다.

 

추가적인 앰배서더 컨테이너를 사용한 curl 파드 실행

앰배서더 컨테이너 패턴을 실제로 보려면 앞에서 만든 curl 파드와 같은 파드를 새로 생성 해야 하지만, 이번에는 파드에서 단일 컨테이너를 실행하는 대신 제가 공부하고 있는 책의 저자가 이미 만들어서 도커 허브에 푸시해놓은 다목적 kubect1-proxy 컨테이너 이미지를 기반으로 추가적인 앰배서더 컨테이너를 실행하겠습니다. 직접 빌드하려는 경우 코드 아카이브(/Chapter08/kubectl_ proxy/)에서 이미지의 Dockerfile을 찾을 수 있을 것입니다.

 

파드의 매니페스트는 다음 예제와 같습니다.

 

예제 1) 앰배서더 컨테이너가 있는 파드: curl-with-ambassador.yml

apiVersion: v1
kind: Pod
metadata:
  name: curl-with-ambassador
spec:
  containers:
    - name: main
      image: tutum/curl
      command: ["sleep", "9999999"]
    - name: ambassador                 # kubectl-proxy 이미지를 실행하는 앰배서더 컨테이너
      image: luksa/kubectl-proxy:1.6.2

 

파드 스펙은 이전과 거의 동일하지만 파드 이름과 추가적인 컨테이너가 다릅니다. 파드를 실행한 다음 main 컨테이너로 들어갑니다.

 

$ kubectl exec -it curl-with-ambassador -c main bash
root@curl-with-ambassador:/#

 

이제 파드에는 두 개의 컨테이너가 있으며 main 컨테이너에서 bash를 실행하려면 -c main 옵션이 필요합니다. 파드의 첫 번째 컨테이너에서 명령을 실행하려는 경우 컨테이너를 명시적으로 지정할 필요는 없습니다. 그러나 다른 컨테이너 내에서 명령을 실행하려면 -c 옵션 을 사용해 컨테이너 이름을 지정해야 합니다.

 

앰배서더를 통한 API 서버와의 통신

이제 앰배서더 컨테이너로 API 서버에 접속할 수 있습니다. 기본적으로 kubectl proxy는 포트 8001에 바인딩되며, 파드의 두 컨테이너 모두 루프백을 포함해 동일한 네트워크 인터페이스를 공유하므로 다음 예제와 같이 curl로 localhost:8001에 접속할 수 있다.

 

예제 2) 앰배서더 컨테이너로 API 서버 액세스하기

root@curl-with-ambassador:/# curl localhost:8001
"paths": [
"/api",
...
]
}

 

 curl로 출력된 결과는 앞에서 본 것과 동일한 응답이지만 이번에는 인증 토큰 및 서버 인증서를 처리할 필요가 없습니다.

 

정확히 무슨 일이 일어났는지 명확하게 파악하려면 아래 그림을 보면 이해가 쉬울 것입니다. curl은 (인증 헤더 없이) 일반 HTTP 요청을 앰배서더 컨테이너 내에서 실행 중인 프록시로 전송한 다음, 프록시는 HTTPS 요청을 API 서버로 전송하며, 토큰을 전송해 클라이언트 인증을 처리하 고 서버의 인증서를 검증해 서버의 신원을 확인합니다.

 

이것은 외부 서비스에 연결하는 복잡성을 숨기고 메인 컨테이너에서 실행되는 애플리케이션을 단순화하기 위해 앰배서더 컨테이너를 사용하는 좋은 예시입니다. 앰배서더 컨테이 너는 메인 애플리케이션의 언어에 관계없이 여러 애플리케이션에서 재사용할 수 있습니다. 단점은 추가 프로세스가 실행 중이고 추가 리소스를 소비한다는 것입니다.

▲ 앰배서더 컨테이너의 kubectl proxy로 암호화, 인증, 서버 검증 부하를 줄입니다.

 

클라이언트 라이브러리를 사용해 API 서버와 통신

애플리케이션이 API 서버와 간단한 몇 가지 작업만 수행하면 되는 경우, 특히 이전 예제에 서 했던 방식과 같이 kubectl-proxy 앰배서더 컨테이너를 이용하면 일반적인 HTTP 클라이언트 라이브러리를 사용해서 간단히 HTTP 요청을 수행할 수 있습니다. 그러나 단순한 API 요청 이상을 수행하려면 쿠버네티스 API 클라이언트 라이브러리 중 하나를 사용하는 것이 좋습니다.

 

클라이언트 라이브러리 사용

현재 API Machinery SIG Special Interest Group에서 지원하는 Kubernetes API 클라이언트 라이브러리는 두 가지가 있습니다.

 공식적으로 지원되는 두 개의 라이브러리 외에도 다른 여러 언어에 관한 사용자 제공 클라이언트 라이브러리 목록이 다음과 같이 있습니다.

 

 

이 라이브러리는 일반적으로 HTTPS를 지원하고 인증을 관리하므로 앰배서더 컨테이너를 사용할 필요가 없습니다. 이 외에도 쿠버네티스 커뮤니티에는 쿠버네티스의 특정 영역에 초점을 맞춘 다수의 SIG와 워킹 그룹 (Working Group)이 있습니다. https://github.com/kubernetes/community/blob/master/sig-list.md에서 목록을 찾을 수 있습니다.

 

Fabric Java Client를 사용한 쿠버네티스와의 상호작용 예시

클라이언트 라이브러리를 사용해 API 서버와 통신하는 방법을 이해하기 위해 다음 예제는 Fabric쿠버네티스 클라이언트를 사용해 Java 애플리케이션에서 서비스를 나열하는 예시를 보여줍니다.

 

예제 3) Fabric8 Java client를 사용한 파드의 나열, 생성, 업데이트, 삭제

import java.util.Arrays;
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.api.model.PodList;
import io.fabric8.kubernetes.client.DefaultKubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClient;

public class Test {
    public static void main(String[] args) throws Exception {
        KubernetesClient client = new DefaultKubernetesClient();

        // List pods in the default namespace
        PodList pods = client.pods().inNamespace("default").list();
        pods.getItems().stream()
                .forEach(s -> System.out.println("Found pod: " + s.getMetadata().getName()));

        // Create a pod
        System.out.println("Creating a pod");
        Pod pod = client.pods().inNamespace("default")
        	.createNew()
        	.withNewMetadata()
        		.withName("programmatically-created-pod")
            .endMetadata()
            .withNewSpec()
                .addNewContainer()
                	.withName("main")
                	.withImage("busybox")
                	.withCommand(Arrays.asList("sleep", "99999"))
                .endContainer()
            .endSpec()
            .done();
        System.out.println("Created pod: " + pod);

        // Edit the pod (add a label to it)
        client.pods().inNamespace("default")
            .withName("programmatically-created-pod")
            .edit()
            .editMetadata()
                .addToLabels("foo", "bar")
              .endMetadata()
              .done();
        System.out.println("Added label foo=bar to pod");

        System.out.println("Waiting 1 minute before deleting pod...");
        Thread.sleep(60000);

        // Delete the pod
        client.pods().inNamespace("default")
                .withName("programmatically-created-pod")
                .delete();
        System.out.println("Deleted the pod");
    }
}

 

Fabric8 클라이언트는 훌륭하고 유창한 도메인 특화 언어(DSL, Domain-Specific-Language) API를 제공하기 때문에 코드를 자체적으로 설명되도록 작성해야 합니다. 또한 가독성이 좋고 이해하기 쉽습니다.

 

스웨거와 OpenAPI를 사용해 자신의 라이브러리 구축

선택한 프로그래밍 언어에 사용할 수 있는 클라이언트가 없는 경우 스웨거 Swagger API 프 레임워크를 사용해 클라이언트 라이브러리와 문서를 생성할 수 있다. 쿠버네티스 API 서버는/swaggerapi에서 스웨거 API 정의를 공개하고 /swagger.json에서 OpenAPI 스펙을 공개합니다.

 

스웨거 프레임워크에 대한 자세한 내용을 보려면 웹사이트(http://swagger.io)를 방문하여 확인할 수 있습니다.

 

스웨거 UI로 API 살펴보기

이전 포스팅 앞부분에서 curl을 이용해 REST 엔드포인트에 접속하는 대신, REST API를 탐색하는 더 좋은 방법을 알려준다고 언급했습니다. 이전 절에서 언급한 스웨거는 API를 설정하기 위한 도구일 뿐만 아니라 스웨거 API 정의를 공개하는 경우 REST API를 탐색하기 위한 웹 UI도제공합니다. 이 UI로 REST API를 더 나은 방식으로 탐색할 수 있습니다.

 

쿠버네티스는 스웨거 API가 공개돼 있는 데다가 API 서버에 스웨거 UI도 통합돼 있지만 기본적으로 활성화돼 있진 않습니다. API 서버를 --enable-swagger-ui=true 옵션으로 실 행하면 활성화할 수 있습니다. UI를 활성화한 후 브라우저에서 다음 URL로 접속해 UI를 실행할 수 있습니다.

 

http(s)://<api 서버>: <port>/swagger-ui

 

스웨거 UI를 사용해보셨으면 합니다. 쿠버네티스 API를 탐색할 수 있을 뿐만 아니라 API MA 와 상호작용할 수도 있습니다(예를 들어, JSON 리소스 매니페스트를 POST하고 리소스를 PATCH 또는 DELETE할 수 있다).

 

이번 포스팅은 여기서 마무리하도록 하겠습니다. 끝까지 읽어주셔서 감사합니다!

반응형
반응형

쿠버네티스 API 서버와 통신하기

이전 Downward API와 메타데이터 포스팅에서 DwonwardAPI가 어떻게 특정 파드와 컨테이너의 메타데이터를 그 안에서 실행되는 프로세스를 쉽게 전달하는지 살펴봤습니다. Downward API는 단지 파드 자체의 메타데이터와 모든 파드의 데이터 중 일부만 노출합니다. 그러나 때때로 애플리케이션에서 클러스터에 정 의된 다른 파드나 리소스에 관한 더 많은 정보가 필요할 수도 있습니다. 이 경우 Downward API는 도움이 되지 않습니다.

 

서비스와 파드에 관한 정보는 서비스 관련 환경변수나 DNS로 얻을 수 있습니다. 그러나 애플리케이션이 다른 리소스의 정보가 필요하거나 가능한 한 최신 정보에 접근해야 하는 경우 API 서버와 직접 통신해야 합니다.

 

파드 내의 애플리케이션이 쿠버네티스 API 서버와 통신하는 방법을 살펴보기 전에 먼저 로컬 컴퓨터에서 서버의 REST 엔드포인트를 살펴본 후 API 서버와 통신하는 방법을 확인해보겠습니다.

 

쿠버네티스 REST API 살펴보기

쿠버네티스 API와 통신 하는 애플리케이션을 개발하기 위해 API에 관해 먼저 알아봐야 합니다. API에 알아보기 위해서 API 서버에 직접 접속해 보는 방법이 있습니다. kubectl cluster-info를 실행해 URL을 얻을 수 있습니다.

 

$ kubectl cluster-info
Kubernetes master is running at https://192.168.99.100:8443

 

 서버는 HTTPS를 사용하고 인증이 필요하기 때문에 직접 통신하는 것은 간단하지 않습니다. curl을 사용해 접속하고 curl의 --insecure(또는 -k) 옵션을 사용해 서버 인증서 확인을 건너뛰도록 시도해볼 수 있지만 원하는 결과를 얻지는 못할 것 입니다.

 

$ curl https://192.168.99.100:8443-k
Unauthorized

 

하지만 다행히도 인증을 직접 처리하는 대신 kubectl proxy 명령을 실행해 프록시로 서버와 통신할 수 있습니다.

 

kubectl 프록시로 API 서버 액세스하기

kubectl proxy 명령은 프록시 서버를 실행해 로컬 컴퓨터에서 HTTP 연결을 수신하고 이 연결을 인증을 관리하면서 API 서버로 전달하기 때문에, 요청할 때마다 인증 토큰을 전달할 필요가 없습니다. 또한 각 요청마다 서버의 인증서를 확인해 중간자(man in the middle)가 아닌 실제 API 서버와 통신한다는 것을 담보합니다.

 

프록시를 실행하는 것은 생각보다 아주 쉽습니다. 다음 순서로 명령어를 입력해줍니다.

 

$ kubectl proxy
Starting to serve on 127.0.0.1:8001

 

 kubectl은 필요한 모든 것(API 서버 URL, 인증 토큰 등)을 이미 알고 있으므로 다른 인자 를 전달할 필요가 없습니다. 시작하자마자 프록시는 로컬 포트 8001에서 연결을 수신하기 시작합니다. 잘 작동하는지 살펴봅시다.

 

$ curl localhost:8001
{
paths": [
"/api",
"/api/v1",
...

 

이렇게 요청을 프록시로 보내면 이 요청을 API 서버로 보낸 다음 프록시는 서버가 반환하는 모든 것을 반환하게 됩니다. 이제 내용을 살펴보겠습니다.

 

kubectl proxy로 쿠버네티스 API 살펴보기

curl을 계속 이용하거나 웹 브라우저를 열어 http://localhost:8001로 접속할 수 있게 되었습니다. 기본 URL에 접속해 API 서버가 무엇을 반환하는지 좀 더 면밀히 살펴보겠습니다. 서버는 다음 예제와 같이 경로 목록을 반환합니다.

 

  • 예제 1) API 서버의 REST 엔드포인트 목록: http://localhost:8001
$ curl http://localhost:8081
{
"paths": [
"/api",                     
"/api/v1",                            > 대부분의 리소스 타입을 여기서 확인할 수 있습니다.
"/apis",
"/apis/apps",
"/apis/apps/v1beta1",
...
"/apis/batch",                     > batch API 그룹과 이 그룹의 두 가지 버전
"/apis/batch/v1",
"/apis/batch/v2alpha1",

 

 이러한 경로는 파드, 서비스 등과 같은 리소스를 생성할 때 리소스 정의에 지정한 API 그룹과 버전에 해당합니다. 이전 쿠버네티스 포스팅들을 정독했다면 /apis/batch/v1 경로의 batch/v1가 잡 리소스의 API 그룹과 버전이란 것을 알아차릴 수 있을 것입니다.

 

마찬가지로 /api/v1은 생성한 일반적인 리소스(Pods, Services, ReplicationController 등)에서 참조하는 apiVersion: v1에 해당합니다. 초기의 쿠버 네티스는 API 그룹 개념을 사용하지 않았기 때문에, 초기 버전의 쿠버네티스에 도입된 가장 일반적인 리소스 유형들은 특정 그룹에 속하지 않습니다. API 그룹은 나중에 도입됐습니다. 때문에 API 그룹이 없는 초기 리소스 유형은 이제 core API 그룹에 속하는 것으로 간주됩니다.

 

  • 쿠버네티스에 관한 다른 글들은 다음 페이지에 정리해되어있습니다.
 

'Container/Kubernetes' 카테고리의 글 목록

IT에 입문하며 배우는 것들과 뉴스들을 요약해 올리고 있습니다. 모든 방문자들에게 감사를 표하며 행운이 가득하길.

easyitwanner.tistory.com

 

배치 API 그룹의 REST 엔드포인트 살펴보기

잡 리소스 API를 살펴보겠습니다. 다음 예제에 표시된 대로 /apis/batch 경로 뒤에 무엇이 있는지 살펴보겠습니다. 이 예제에서는 버전이 생략되었습니다.

 

  • 예제 2) /apis/batch 아래 있는 엔드포인트 조회: http://localhost:8001/apis/batch
$ curl http://localhost:8001/apis/batch
{
  "kind": "APIGroup",
  "apiVersion": "v1",
  "name": "batch",
  "versions": [
    {
      "groupVersion": "batch/v1",                 >  두 가지 버전을 갖는 batch API 그룹
      "version": "v1"
    },
    {
      "groupVersion": "batch/v2alpha1",       > 두 가지 버전을 갖는 batch API 그룹
      "version": "v2alpha1"
    }
  ],
  "preferredVersion": {
    "groupVersion": "batch/v1",                     > 클라이언트는 v2alpga1 대신 v1 버전을 사용해야 합니다.
    "version": "v1"
  },
  "serverAddressByClientCIDRs": null
}

 

 응답에는 사용 가능한 버전과 클라이언트가 사용해야 하는 선호 버전에 관한 정보와 batch API 그룹에 관한 설명이 표시됩니다. 계속해서 /apis/batch/v1 경로 뒤에 무엇이 있는지 살펴보겠습니다.

 

  • 예제 3) batch/v1 내의 리소스 유형: http://localhost:8001/apis/batch/v1
$ curl http://localhost:8001/apis/batch/v1
{
  "kind": "APIResourceList",     >  batch/v1 API 그룹 내의 API 리소스 목록
  "apiVersion": "v1",
  "groupVersion": "batch/v1",
  "resources": [                         <  이 그룹의 모든 리소스 유형을 담는 배열
    {
      "name": "jobs",
      "namespaced": true,          >  네임스페이스 지정(namespaced) 필드가 true인 잡 리소스에 관한 설명
      "kind": "Job",
      "verbs": [
        "create",
        "delete",
        "deletecollection",            >  이 리소스와 함께 사용할 수 있는 동사들은 다음과 같습니다(잡을 생성할 수 있고,
        "get",                                    각각 또는 여러 개를 한꺼번에 삭제할 수 있으며 검색, 감시, 업데이트할 수 
        "list",                                    있습니다).
        "patch",
        "update",
        "watch"
      ]
    },
    {
      "name": "jobs/status",         <  리소스는 상태를 수정하기 위한 특수한 REST 엔드포인트가 있습니다.
      "namespaced": true,
      "kind": "Job",
      "verbs": [
        "get",                                 >   상태 정보는 검색, 패치, 업데이트할 수 있습니다.
        "patch",
        "update"
      ]
    }
  ]
}

 

보다시피 API 서버는 batch/v1 API 그룹에서 리소스 유형 및 REST 엔드포인트 목록을 반환합니다. 그중 하나가 잡 리소스입니다. API 서버는 리소스 이름과 관련 kind 외에도 리소 스에 네임스페이스가 지정됐는지(namespaced) 여부, 짧은 이름(있는 경우에 표시합니다. 잡은 짧은 이름이 없습니다)과 해당 리소스에 사용할 수 있는 동사(verbs) 목록도 갖고 있습니다.

 

반환된 목록은 API 서버에 노출된 REST 리소스를 설명합니다. "name": "jobs"는 API 에/apis/batch/v1/jobs 엔드포인트가 포함돼 있음을 알려줍니다. "verbs" 배열은 해당 엔 드포인트로 잡 리소스를 검색, 업데이트, 삭제할 수 있음을 알려줍니다. 특정 리소스의 경우 추가 API 엔드포인트도 노출됩니다(예: iohs/status 경로는 잡의 상태만 변경 가능합니다).

 

클러스터에 있는 모든 잡 인스턴스 나열하기

클러스터에서 잡목록을 얻으려면 다음 예제와 같이 /apis/batch/v1/jobs 경로에서 GET 요청을 실행해야 합니다.

  • 예제 4) 잡 목록: http://localhost:8001/api/s/batch/v1/jobs
$ curl http://localhost:8001/apis/batch/v1/jobs
{
    "kind": "JobList",
    "apiVersion": "batch/v1",
    "metadata": {
        "selfLink": "/apis/batch/v1/jobs",
        "resourceVersion": "225162"
},
"items": [
{
    "metadata": {
        "name": "my-job",
        "namespace": "default",

 

 클러스터에 잡 리소스가 배포돼 있지 않기 때문에 항목(items) 배열이 비어 있을 것입니다. Chapter08/my-job.yaml에 있는 잡을 배포하고 REST 엔드포인트를 다시 접속하면 위 예제 4와 동일한 결과를 얻을 수 있습니다.

 

이름별로 특정 잡 인스턴스 검색

앞 예제의 엔드포인트는 모든 네임스페이스의 모든 잡 목록을 반환했습니다. 하나의 특정 잡을 반환하려면 URL에 이름과 네임스페이스를 지정해야 합니다. 앞 예제에 표시된 잡(name: my- job; namespace: default)을 검색하려면 다음 예제와 같이 /apis/batch/v1/namespaces/default/jobs/my-job 경로를 요청해야 합니다.

 

  • 예제 5) 이름으로 특정 네임스페이스에 있는 리소스 검색
$ curl http://localhost:8001/apis/batch/v1/namespaces/default/jobs/my-job
{
    "kind": "Job",
    "apiVersion": "batch/v1",
    "metadata": {
        "name":"my-job",
        "namespace": "default",

 

 보다시피 다음 명령을 실행한 것과 정확히 동일하게 my-job 잡 리소스에 관한 전체 JSON 정의를 얻을 수 있습니다.

 

$ kubectl get job my-job -o json

 

특별한 도구를 사용하지 않고도 쿠버네티스 REST API 서버를 탐색할 수 있지만, REST API 전체를 탐색하고 상호작용하기 위한 더 나은 옵션은 나중에 다뤄보도록 하겠습니다. 지금은 이렇게 curl을 사용해 둘러보는 것만으로도 파드에서 실행되는 애플리케이 션이 쿠버네티스와 어떻게 통신하는지 이해하기에 충분할 것입니다.

 

파드 내에서 API 서버와 통신

kubectl proxy를 사용해 로컬 컴퓨터에서 API 서버와 통신하는 방법을 배웠습니다. 이제 자주 사용되는 kubectl이 없는 파드 내에서 통신하는 방법을 알아보겠습니다. 파드 내부에서 API 서버와 통신하려면 다음 세 가지를 처리해야 합니다.

 

  •  API 서버의 위치를 찾아야 합니다.
  •  API 서버인 척 가장하는 누군가와 통신하는 것이 아니라) API 서버와 통신하고 있는지 확인해야 합니다.
  •  API 서버로 인증해야 한다. 그렇지 않으면 볼 수도 없고 아무것도 할 수 없습니다.

 다음 부분에서 이 작업을 수행하는 방법을 살펴보겠습니다.

 

API 서버와의 통신을 시도하기 위해 파드 실행

 

가장 먼저 필요한 것은 API 서버와 통신할 파드입니다. (sleep 명령을 실행하는 컨테이너 하나만 있는) 아무것도 하지 않는 파드를 실행한 다음, 컨테이너의 셸에서 kubectl exec를 실행한다. 그런 다음 curl을 사용해 해당 셸 내에서 API 서버에 액세스할 것입니다.

 

curl 바이너리가 포함된 컨테이너 이미지를 사용해야 합니다. 도커 허브에서 tutum/ curl 이미지를 검색해서 사용할 수 있습니다(curl 바이너리를 포함하는 다른 기존 이미지를 사용하거나 직접 만들 수도 있습니다). 파드 정의는 다음 예제에 표시돼 있습니다.

 

  • 예제 6) API 서버와 통신을 시도하는 파드: curl.yaml
apiVersion: v1
kind: Pod
metadata:
  name: curl
spec:
  containers:
    - name: main
      image: tutum/curl                        >  컨테이너에서 curl을 사용해야 하기 때문에 tutum/curl 이미지를 사용합니다.
      command: ["sleep", "9999999"]  >  컨테이너가 계속 실행되도록 하려고 지연 시간이 길게
                                                              sleep 커맨드 실행합니다.

 

파드를 만든 후 kubectl exec를 실행해 컨테이너 내부에서 bash 셸을 실행합니다.

 

$ kubectl exec -it curl bash
root@curl:/#

 

이제 API 서버와 통신할 준비가 됐습니다.

 

API 서버 주소 찾기

 

먼저 쿠버네티스 API 서버의 IP와 포트를 찾아야 합니다. kubernetes라는 서비스가 디폴트 네임스페이스에 자동으로 노출되고 API 서버를 가리키도록 구성되기 때문에 쉽습니다. kubectl get svc를 사용해 서비스를 조회할 수 있습니다.

 

$ kubectl get svc

 

그리고 각 서비스에 관해 환경변수가 구성돼 있습니다. API 서버의 IP 주소와 포트를 컨테이너 내부의 KUBERNETES_SERVICE_HOST와 KUBERNETES_SERVICE _PORT 변수에서 모두 얻을 수 있습니다.

 

root@curl:/#env | grep KUBERNETES_SERVICE
KUBERNETES_SERVICE_PORT-443
KUBERNETES_SERVICE_HOST=10.0.0.1
KUBERNETES_SERVICE_PORT_HTTPS-443

 

또한 각 서비스마다 DNS 엔트리가 있으므로 환경변수를 조회할 필요도 없이, 단순히 curl에서 https://kubernetes를 가리키기만 하면 됩니다. 서비스가 어느 포트에서 제공되는지 모를 경우 서비스의 실제 포트 번호를 얻기 위 해 환경변수를 조회하거나 DNS SRV 레코드 조회를 수행해 확인할 수 있습니다.

 

위 예제에 표시된 환경변수에 따르면 API 서버가 HTTPS의 기본 포트인 443에서 수 신 대기 중이므로 HTTPS로 서버에 접속할 수 있습니다.

 

root@curl:/# curl https://kubernetes
curl: (60) SSL certificate problem: unable to get local issuer certificate
...
If you'd like to turn off curl's verification of the certificate, use the-k (or --insecure) option.

 

 이 문제를 해결하는 가장 간단한 방법은 제안된 -K 옵션을 사용하는 것(그리고 이것이 API 서버를 수동으로 사용할 때 일반적으로 사용하는 방법)이지만, 좀 더 길어도 올바른 방법을 살펴보겠습니다. 연결하려는 서버가 인증된 API 서버라는 것을 맹목적으로 신뢰하는 대신 인증서를 curl로 검사해 인증서를 확인합니다.

 

실제 애플리케이션에서는 서버 인증서 확인을 절대로 건너뛰면 안 됩니다. 그렇게 하면 중간자 공격(man-in-the-middle attack)으로 애플리케이션의 인증 토큰을 공격자에게 노출시킬 수 있습니다.

 

  • '중간자 공격'은 통신을 연결하는 두 사람 사이에 중간자가 침입해 두 사람은 상대방에게 연결했다고 생각하지만 실제로는 두 사 람은 중간자에게 연결돼 있으며 중간자가 한쪽에서 전달된 정보를 도청 및 조작한 후 다른 쪽으로 전달하는 것을 말합니다.

서버의 아이덴티티 검증

 

이전에 시크릿을 설명하면서 각 컨테이너의 /var/run/secrets/kubernetes.io/service account/에 마운트되는 자동으로 생성된 "default-token-cfee9"라는 이름의 시크릿을 살펴 봤습니다. 해당 디렉터리의 파일을 조회해 해당 시크릿의 내용을 다시 살펴보겠습니다. 시크릿에 대한 설명은 다음 페이지에서 다룬적 있으니 참고 바랍니다.

 

[Kubernetes Secrets] 쿠버네티스 시크릿(Secret)이란?

목차 시크릿 기본 토큰 시크릿 시크릿 생성 컨피그맵과 시크릿 비교 stringData 필드 파드에서 시크릿 항목 읽기 파드에서 시크릿 사용 HTTPS를 활성화하도록 fortune-config 컨피그맵 수정 fortune-https 시

easyitwanner.tistory.com

 

root@curl:/#1s/var/run/secrets/kubernetes.io/serviceaccount/
ca.crt namespace token

 

 시크릿에는 세 개의 항목이 있습니다(그래서 시크릿 볼륨에 세 개의 파일이 있다). 지금은 쿠버네 티스 API 서버의 인증서에 서명하는 데 사용되는 인증 기관(CA)의 인증서를 보유한 ca.crt 파일에 집중하려 합니다. API 서버와 통신 중인지 확인하려면 서버의 인증서가 CA로 서명 됐는지 확인해야 합니다. curl을 --cacert 옵션과 같이 사용하면 CA 인증서를 지정할 수 있으므로 API 서버를 다시 접속합니다.

 

root@curl:/# curl --cacert/var/run/secrets/kubernetes.io/serviceaccount/ca/crt https://kubernetes
Unauthorized

 

  • 이 때 "Unauthorized"보다 더 긴 오류 설명이 표시될 수도 있습니다.

 

서버의 인증서를 신뢰할 수 있는 CA가 서명했기 때문에 curl이 서버의 ID를 확인했습니다. Unauthorized에서 알 수 있듯이 여전히 인증 처리가 필요합니다. 잠시 뒤 시도해보겠지만 먼저 CURL_CA_BUNDLE 환경변수를 설정해 편하게 할 수 있는 방법을 살펴보겠습니다. 이렇게 하면 curl을 실행할 때마다 --cacert를 지정할 필요가 없게 됩니다..

 

root@curl:/# export CURL_CA_BUNDLE=/var/run/secrets/kubernetes.io/serviceaccount/ca.

 

--cacert를 사용하지 않고 API 서버에 접속할 수 있다.

 

root@curl:/# curl https://kubernetes
Unauthorized

 

이제 독자의 클라이언트(curl)는 API 서버를 신뢰하지만 API 서버 자체는 여러분이 누구인지 모르기 때문에 액세스 권한이 없다고 표시할 것입니다.

 

API 서버로 인증

 

서버에서 인증을 통과해야 클러스터에 배포된 API 오브젝트를 읽고, 업데이트와 삭제를 할 수 있습니다. 인증하려면 인증 토큰이 필요합니다. 다행히 토큰은 전에 언급한 default- token 시크릿으로 제공되며 시크릿 볼륨의 token 파일에 저장됩니다. 시크릿 이름에서 알 수 있듯이, 시크릿의 주된 목적이 바로 이것입니다.

 

토큰을 사용해 API 서버에 액세스합니다. 먼저 토큰을 환경변수에 로드해야 합니다.

 

root@curl:/#TOKEN=$(cat/var/run/secrets/kubernetes.io/serviceaccount/token)

 

토큰은 이제 TOKEN 환경변수에 저장됩니다. 다음 예제와 같이 API 서버로 요청을 보낼 때 사용할 수 있습니다.

 

  • 예제 7) API 서버로부터 적절한 응답 받기
root@curl:/# curl -H "Authorization: Bearer $TOKEN" https://kubernetes on
{
  "paths": [
    "/api",
    "/api/v1",
    "/apis",
    "/apis/apps",
    "/apis/apps/v1beta1",
    "/apis/authorization.k8s.io",
    // ...
    "/ui/",
    "/version"
  ]
}

 

보다시피 요청의 Authorization HTTP 헤더 내부에 토큰을 전달했습니다. API 서버는 토큰을 인증된 것으로 인식하고 적절한 응답을 반환했습니다. 이제 앞의 절들에서 수행한 방식으 로 클러스터의 모든 리소스를 탐색할 수 있습니다.

 

예를 들어 동일한 네임스페이스 내에 있는 모든 파드를 조회할 수 있습니다. 그러나 먼저 curl 파드가 어떤 네임스페이스에서 실행 중인지 알아야 합니다.

 

역할 기반 액세스 제어(RBAC) 비활성화

 

RBAC가 활성화된 쿠버네티스 클러스터를 사용하는 경우 서비스 어카운트가 API 서버에 액세스할 권한이 없을 수 있습니다. 서비스 어카운트와 RBAC에 대해서는 나중에 따로 다루겠지만 이 포스팅에서는 API 서버를 쿼리할 수 있는 가장 간단한 방법인 다음 명령을 실행해 RBAC를 우회하는 것에 대해 설명하겠습니다.

$ kubectl create clusterrolebinding permissive-binding\
--clusterrole=cluster-admin \
--group=system:serviceaccounts

 

 이렇게 하면 모든 서비스 어카운트(모든 파드라고도 말할 수 있다)에 클러스터 관리자 권한이 부여돼 원하는 대로 할 수 있습니다. 이렇게 하는 것은 분명히 위험하고 프로덕션 클러스터에서는 절대 해서는 안 됩니다. 단, 테스트 목적이라면 상관없습니다.

 

파드가 실행 중인 네임스페이스 얻기

 

바로 이전 포스팅인 Downward API 부분에서 해당 API로 네임스페이스를 파드에 전달하는 방법을 살펴봤습니다. 여기서 주의 깊게 봤다면 시크릿 볼륨에 네임스페이스라는 파일이 포함돼 있음을 보셨을 수도 있을 것 입니다. 이 파일에는 파드가 실행 중인 네임스페이스가 포함돼 있으므로, 환경변 수로 파드에 네임스페이스를 명시적으로 전달하는 대신 파일을 읽을 수 있습니다. 파일 내용을 NS 환경변수에 로드한 뒤 다음 예제와 같이 모든 파드를 나열해보겠습니다.

 

  • 예제 8) 파드가 속한 네임스페스에 있는 파드 나열하기
root@curl:/#NS=$(cat/var/run/secrets/kubernetes.io/serviceaccount/namespace)
root@curl:/# curl -H "Authorization: Bearer $TOKEN" https://kubernetes/api/v1/namespaces/$NS/pods
{
"kind": "PodList",
"apiVersion": "v1",
...

 

 마운트된 시크릿 볼륨 디렉터리에 있는 3개의 파일을 사용해 파드와 동일한 네임스페 이스에서 실행 중인 모든 파드를 나열했습니다. 같은 방식으로 다른 API 오브젝트를 검색하고 간단한 GET 요청 대신 PUT 또는 PATCH를 전송해 업데이트할 수도 있습니다.

 

파드가 쿠버네티스와 통신하는 방법 정리

파드 내에서 실행 중인 애플리케이션이 쿠버네티스 API에 적절히 액세스할 수 있는 방법을 간단히 정리하면 다음과 같습니다.

 

  • 애플리케이션은 API 서버의 인증서가 인증 기관으로부터 서명됐는지를 검증해야 하며, 인증 기관의 인증서는 ca.cart 파일에 있습니다.
  • 애플리케이션은 token 파일의 내용을 Authorization HTTP 헤더에 Bearer 토큰으로 넣어 전송해서 자신을 인증해야 합니다.
  • namespace 파일은 파드의 네임스페이스 안에 있는 API 오브젝트의 CRUD 작업을 수행할 때 네임스페이스를 API 서버로 전달하는 데 사용해야 합니다.
    • CRUD는 Create, Pead, Update, Delete를 나타냅니다. 해당 HTTP 메서드는 각각 POST. GET, PATCH/PUT, DELETE입니다.

 

 

 

반응형
반응형

 

downwardAPI 볼륨에 파일로 메타데이터 전달

환경변수 대신 파일로 메타데이터를 노출하려는 경우 downwardAPI 볼륨을 정의해 컨테이너에 마운트 할 수 있습니다. 환경변수로 파드의 레이블이나 어노테이션을 노출할 수 없기 때문에 downwardAPI 볼륨을 사용해야 합니다. 이유는 나중에 추가로 다뤄보겠습니다.

 

환경변수와 마찬가지로 메타데이터를 프로세스에 노출시키려면 각 메타데이터 필드를 명시적으로 지정해야 합니다. 다음 예제에서 환경변수 대신 볼륨을 사용하도록 이전 예제를 수정하는 방법을 살펴보겠습니다.

 

예제. 1) downwardAPI 볼륨을 갖는 파드: downward-api-volume.yaml

apiVersion: v1
kind: Pod
metadata:
  name: downward
  labels:          # 
    foo: bar       #
  annotations:     #
    key1: value1   # 이 레이블과 어노테이션은 downwardAPI 볼륨으로 노출됩니다.
    key2: |        #
      multi        #
      line         #
      value        #
spec:
  containers:
    - name: main
      image: busybox
      command: ["sleep", "9999999"]
      resources:
        requests:
          cpu: 15m
          memory: 100Ki
        limits:
          cpu: 100m
          memory: 4M1
      volumeMounts:                  #
        - name: downward             # dwonward 볼륨은 /etc/downward 아래에 마운트합니다.
          mountPath: /etc/downward   #
  volumes:
    - name: downward  # downwardAPI 볼륨을 downward라는 이름으로 정의합니다.
      downwardAPI:    #
        items:
          - path: "podName"               # 파드의 이름(매니페스트에 있는 metadata.name
            fieldRef:                     # 필드에서 확인할 수 있습니다.)은 podName 파일에
              fieldPath: metadata.name    # 기록됩니다.
          - path: "podNamespace"
            fieldRef:
              fieldPath: metadata.namespace
          - path: "labels"                    #
            fieldRef:                         # 파드의 레이블은 /etc.downward/labels 파일에 기록됩니다.
              fieldPath: metadata.labels      #
          - path: "annotations"                   #
            fieldRef:                             # 파드의 어노테이션을
              fieldPath: metadata.annotations     # /ect/downward/annotations 파일에 기록합니다.
          - path: "containerCpuRequestMilliCores"
            resourceFieldRef:
              containerName: main
              resource: requests.cpu
              divisor: 1m
          - path: "containerMemoryLimitBytes"
            resourceFieldRef:
              containerName: main
              resource: limits.memory
              divisor: 1

 

환경변수로 메타데이터를 전달하는 대신 downward라는 볼륨을 정의하고 컨테이너의 /etc/downward 아래에 마운트 합니다. 이 볼륨에 포함된 파일들은 볼륨 스펙의 downward API.items 속성 아래에 설정됩니다.

 

각 항목은 메타데이터를 기록할 경로(파일 이름)와 파일에 저장할 값의 파드 수준의 필 드나 컨테이너 리소스 필드에 대한 참조를 지정합니다.

 

이전에 만든 파드를 삭제하고 이전 예제의 매니페스트를 사용해 새로운 파드를 만듭니다. 그런 다음 마운트된 downwardAPI 볼륨 디렉터리의 내용을 살펴봅시다. /etc/downward/ 아래에 볼륨을 마운트 했으므로 다음 예제에 표시된 것처럼 파일을 나열할 수 있습니다. 또한 컨피그맵과 시크릿 볼륨과 마찬가지로 파드 스펙에서 downwardAPI 볼륨의 defaultMode 속성으로 파일 권한을 변경할 수 있습니다.

 

예제. 2) downwardAPI 볼륨 내의 파일들

$ kubectl exec downward -- 1s -1L /etc/downward
-rw-r--r-- 1 root root 134 May 25 10:23 annotations
-rw-r--r-- 1 root root 2 May 25 10:23 containerCpuRequestMilliCores
-rw-r--r-- 1 root root 7 May 25 10:23 containerMemoryLimitBytes
-rw-r--r-- 1 root root 9 May 25 10:23 labels
-rw-r--r-- 1 root root 8 May 25 10:23 podName
-rw-r--r-- 1 root root 7 May 25 10:23 podNamespace

 

각 파일은 볼륨 정의의 항목(item)에 해당합니다. 그러나 환경변수로는 레이블과 어노 테이션을 노출할 수 없었기 때문에 다음 예제로 노출하려는 내용이 두 파일에 저장돼 있는지 확인할 수 있습니다.

 

예제. 3) downwardAPI 볼륨의 레이블과 어노테이션 표시

$ kubectl exec downward cat /etc/downward/labels
foo="bar"
 
$ kubectl exec downward cat /etc/downward/annotations
key1="value1"
key2="multi\nline\nvalue\n"
kubernetes.io/config.seen="2016-11-28T14:27:45.6649242822"
kubernetes.io/config.source="api"

 

위에서 볼 수 있듯이 각 레이블/어노테이션은 별도의 줄에 키값 형식으로 저장됩니다. 값이 여러 줄인 경우 줄 바꾸기 문자가 \n으로 표시돼 한 줄로 기록됩니다.

 

레이블과 어노테이션 업데이트

파드가 실행되는 동안 레이블과 어노테이션을 수정할 수 있습니다. 레이블이나 어노테이션이 변경될 때 쿠버네티스가 이 값을 가지고 있는 파일을 업데이트해서 파드가 항상 최신 데이터를 볼 수 있도록 합니다. 이것은 레이블과 어노테이션 이 왜 환경변수로 노출될 수 없는지도 설명할 수도 있습니다. 환경변숫값은 나중에 업데이트할 수 없기 때문에 파드의 레이블 또는 어노테이션이 환경변수로 노출된 경우 변경이 발생한 다음에 새로운 값을 노출할 수 있는 방법이 없습니다.

 

볼륨 스펙에서 컨테이너 수준의 메타데이터 참조

이 내용을 마무리하기 전에 한 가지 짚어봐야 할 것은 컨테이너의 리소스 제한 또는 요청(resourceFieldRef를 사용해 수행)과 같은 컨테이너 수준의 메타데이터를 노출하는 경우 다음 예제에 표시된 것처럼 리소스 필드를 참조하는 컨테이너의 이름을 지정해야 합니다.

 

예제. 4) downwardAPI 볼륨에서 컨테이너 수준의 메타데이터 참조

spec:
  volumes:
    - name: downward
      downwardAPI:
        items:
          - path: "containerCpuRequestMilliCores"
            resourceFieldRef:
              containerName: main #컨테이너 이름이 반드시 지정돼야 합니다.
              resource: requests.cpu
              divisor: 1m

 

볼륨이 컨테이너가 아니라 파드 수준에서 정의됐다고 생각하면 그 이유가 분명해집니다. 볼륨 스펙 내에서 컨테이너의 리소스 필드를 참조할 때는 참조하는 컨테이너의 이름을 명시적으로 지정해야 합니다. 컨테이너가 하나인 파드에서도 마찬가지입니다.

 

볼륨을 사용해 컨테이너의 리소스 요청이나 제한을 노출하는 것은 환경변수를 사용하는 것보다 약간 더 복잡하지만 필요할 경우 한 컨테이너의 리소스 필드를 다른 컨테이너에 전달할 수 있는 장점이 있습니다(단, 두 컨테이너는 모두 같은 파드에 있어야 한다). 환경변수로는 컨테이너 자신의 리소스 제한과 요청만 전달할 수 있다.

 

Downward API 사용 시기 이해

 

앞에서 본 바와 같이 Downward API 사용은 그렇게 복잡하지는 않습니다. 이것은 애플리케이션을 쿠버네티스에 독립적 (Kubernetes-agnostic)으로 유지할 수 있게 합니다. 환경변수의 특정 데이터를 활용하는 기존 애플리케이션을 처리할 때 특히 유용합니다. Downward API를 사용하면 애플리케이션을 다시 짜거나 데이터를 가져와서 환경변수에 노출하는 Shell 스크립트를 사용하지 않고도 데이터를 애플리케이션에 노출할 수 있습니다.

 

그러나 Downward API로 사용 가능한 메타데이터는 상당히 제한적입니다. 더 많은 정 보가 필요한 경우 쿠버네티스 API 서버에서 직접 가져와야 한다. 다음 포스팅에서 그 방법에 대해 다뤄보도록 하겠습니다.

반응형
반응형

 

Downward API로 메타데이터 전달

 

오래간만에 다시 쿠버네티스로 돌아왔습니다. 이번 포스팅에서는 메타데이터를 전달하는 방법에 대해 다뤄보도록 하겠습니다. 이전에 저희는 환경변수 또는 컨피그맵과 시크릿 볼륨으로 설정 데이터를 애플리케이션에 전 달하는 방법을 살펴봤습니다. 이는 사용자가 데이터를 직접 설정하거나 파드가 노드에 스케줄링돼 실행되기 이전에 이미 알고 있는 데이터에는 적합합니다.

 

그러나 파드의 IP, 호스트 노드 이름 또는 파드 자체의 이름(파드의 이름이 생성되는 경우. 예를 들어 레플리카셋이나 그와 유사 한 컨트롤러에 의해 파드가 생성되는 경우)과 같이 실행 시점까지 알려지지 않은 데이터의 경우나 파드의 레이블이나 어노테이션과 같이 어딘가에 이미 설정된 데이터일 경우에 동일한 정보를 여러 곳에 반복해서 설정하고 싶지는 않을 것입니다.

 

이 두 가지 문제는 쿠버네티스의 Downward API로 해결할 수 있습니다. 환경변수 또는 (downwardAPI 볼륨 내에 있는) 파일로 파드와 해당 환경의 메타데이터를 전달할 수 있습니다. 이 때 이름 때문에 혼동 하지 말아야 합니다. Downward API는 애플리케이션이 호출해서 데이터를 가져오는 REST 엔드포인트와 다릅니다. 다음 그림과 같이 환경변수 또는 파일에 파드의 스펙 또는 상태값이 채워지 도록 하는 방식입니다.

 

사용 가능한 메타데이터 이해

Downward API를 사용하면 파드 자체의 메타데이터를 해당 파드 내에서 실행 중인 프로세스에 노출할 수 있다. 현재 다음 정보를 컨테이너에 전달할 수 있습니다.

  • 파드의 이름
  • 파드의 IP 주소
  • 파드가 속한 네임스페이스
  • 파드가 실행 중인 노드의 이름
  • 파드가 실행 중인 서비스 어카운트 이름
  • 각 컨테이너의 CPU와 메모리 요청
  • 각 컨테이너의 CPU와 메모리 제한
  • 파드의 레이블
  • 파드의 어노테이션

 

서비스 어카운트(Service Account), CPU/메모리 요청 (request)과 제한(limit)을 제외하고는 목록에 있는 대부분의 항목은 별도의 설명 없이도 보는데 문제는 크게 없을 것입니다. 이에 대한 내용은 이후에 쿠버네티스를 진행하면서 천천히 다룰 여정이고 간단하게 설명하자면 다음과 같습니다.

 

  • 서비스 어카운트: 파드가 API 서버와 통신할 때 인증하는 계정
  • CPU/메모리 요청 및 제한: 컨테이너에 보장되는 CPU와 메모리의 양과 컨테이너가 얻을 수 있는 최대 양

 

목록에 있는 대부분의 항목은 환경변수 또는 downwardAPI 볼륨으로 컨테이너에 전 달될 수 있지만 레이블과 어노테이션은 볼륨으로만 노출될 수 있습니다. 일부 데이터는 다른 방법(이를테면 운영체제에서 직접)으로도 얻을 수 있지만, Downward API는 더 간단한 대안 을 제공합니다.

 

다음 항목에서 컨테이너 내부에 있는 프로세스에 메타데이터를 전달하는 예를 살펴보겠습니다.

 

환경변수로 메타데이터 노출하기

먼저 환경변수로 파드와 컨테이너의 메타데이터를 컨테이너에 전달하는 방법을 살펴보겠습니다. 다음 예제의 매니페스트를 이용해 컨테이너가 하나인 간단한 파드를 만들 수 있습니다.

 

예시) Downward API: downward-api-env.yaml

apiVersion: v1
kind: Pod
metadata:
  name: down
spec:
  containers:
  - name: main
    image: busybox
    command: ["sleep", "9999999"]
    resources:
      requests:
        cpu: 15m
        memory: 100Ki
      limits:
        cpu: 100m
        memory: 4Mi
    env:
    - name: POD_NAME
      valueFrom:              >  특정 값을 설정하는 대신 파드 매니페스트의 metadata.name을 참조한다.
        fieldRef:
          fieldPath: metadata.name
    - name: POD_NAMESPACE
      valueFrom:
        fieldRef:
          fieldPath: metadata.namespace
    - name: POD_IP
      valueFrom:
        fieldRef:
          fieldPath: status.podIP
    - name: NODE_NAME
      valueFrom:
        fieldRef:
          fieldPath: spec.nodeName
    - name: SERVICE_ACCOUNT
      valueFrom:
        fieldRef:
          fieldPath: spec.serviceAccountName
    - name: CONTAINER_CPU_REQUEST_MILLICORES
      valueFrom:    >  컨테이너의 CPU/메모리 요청과 제한은 fieldRef CHA resourceFieldRef  사용해 참조한다.
        resourceFieldRef:
          resource: requests.cpu
          divisor: 1m   >  리소스 필드의 경우 필요한 단위의 값을 얻으려면 제수(divisor)를 정의한다.
    - name: CONTAINER_MEMORY_LIMIT_KIBIBYTES
      valueFrom:
        resourceFieldRef:
          resource: limits.memory
          divisor: 1Ki

 

프로세스가 실행되면 파드 스펙에 정의한 모든 환경변수를 조회할 수 있습니다. 파드의 이름, IP와 네임스페이스는 각각 POD_NAME, POD_IP와 POD_NAMESPACE 환경변수로 노출됩니다. 컨테이너가 실행 중인 노드의 이름은 NODE_NAME 변수로 노출됩니다. 서비스 어카운트 이름은 SERVICE_ACCOUNT 환경변수 로 사용 가능합니다. 그리고 이 컨테이너에 요청된 CPU 양과 컨테이너가 사용할 수 있는 최대 메모리 양을 갖는 두 개의 환경변수를 작성합니다.

 

자원 제한(limits) 또는 요청(requests)을 노출시키는 환경변수의 경우 제수(divisor)를 지정할 수 있습니다. 제한 또는 요청의 실제 값은 제수로 나누고 결과값을 환경변수로 노출합니다. 이 예제에 서는 CPU 요청에 대한 제수를 1m(1밀리코어 또는 1000분의 1 CPU 코어)로 설정했습니다. CPU 요청을 15m로 설정했기 때문에 환경변수 CONTAINER_CPU_REQUEST_MILLICORES는 15로 설정 됩니다. 마찬가지로 메모리 제한을 4Mi(4 메비바이트, Mebibyte)로 설정하고 제수를 1Ki(1 키비바이트,Kibibyte)로 설정했으므로 CONTAINER_MEMORY_LIMIT_KIBIBYTES 환경변수는 4096으로 설정됩니다.

 

  • 제수는 어떤 수를 나누는 수로, 여기서는 CPU나 메모리를 요청하거나 제한할 때 사용하는 단위 정도로 이해하면 됩니다

 

CPU 제한과 요청의 제수는 1(전체 코어 하나를 의미함) 또는 1m(1밀리코어)로 설정합니다. 메모리 제한/요청의 제수는 1바이트), 1k(킬로 바이트) 또는 1Ki(키비 바이트), 1M(메가 바이트) 또는 1Mi(메비 바이트) 등이 될 수 있습니다.

 

파드를 만든 후에는 kubectl exec를 사용해 다음 예제와 같이 컨테이너에 있는 모든 환경변수를 볼 수 있습니다.

 

예제2) downward 파드 내의 환경변수

$ kubectl exec downward env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/biniyoole
HOSTNAME=downward
CONTAINER_MEMORY_LIMIT_KIBIBYTES-4096
POD_NAME=downward
POD_NAMESPACE=default
POD_IP=10.0.0.10
NODE_NAME=gke-kubia-default-pool-32a2cac8-sg17
SERVICE_ACCOUNT=default
CONTAINER_CPU_REQUEST_MILLICORES=15
KUBERNETES_SERVICE_HOST=10.3.240.1
KUBERNETES_SERVICE_PORT=443
...

 

컨테이너 내부에서 실행되는 모든 프로세스는 해당 변수를 읽을 수 있고, 필요한 대로 사용할 수 있습니다.

 

 

반응형
반응형

목차

  1. 시크릿
  2. 기본 토큰 시크릿
  3. 시크릿 생성
  4. 컨피그맵과 시크릿 비교
    1. stringData 필드
    2. 파드에서 시크릿 항목 읽기
  5. 파드에서 시크릿 사용
    1. HTTPS를 활성화하도록 fortune-config 컨피그맵 수정
    2. fortune-https 시크릿을 파드에 마운트
    3. Nginx가 시크릿의 인증서와 키를 사용하는지 테스트
    4. 시크릿 볼륨을 메모리에 저장하는 이유
    5. 환경변수로 시크릿 항목 노출
  6. 이미지를 가져올 때 사용하는 시크릿
    1. 도커 허브에서 프라이빗 이미지 사용
    2. 도커 레지스트리 인증을 위한 시크릿 생성
    3. 파드 정의에서 도커 레지스트리 시크릿 사용

시크릿

지금까지 컨테이너에 전달한 정보는 보안을 유지할 필요가 없는 일반적이고 민감하지 않은 데이터였습니다. 그러나 설정 안에는 보안이 유지돼야 하는 자격증명과 개인 암호화 키와 같은 민감한 정보도 포함돼 있습니다.

 

이러한 정보를 보관하고 배포하기 위해 쿠버네티스는 시크릿이라는 별도 오브젝트를 제공 합니다. 시크릿은 키-값 쌍을 가진 맵으로 컨피그맵과 매우 비슷하고 또한 컨피그맵과 같은 방식으로 사용할 수 있습니다. 시크릿을 다음과 같은 상황에서 사용할 수 있습니다.

  • 환경변수로 시크릿 항목을 컨테이너에 전달
  • 시크릿 항목을 볼륨 파일로 노출

쿠버네티스는 시크릿에 접근해야 하는 파드가 실행되고 있는 노드에만 개별 시크릿을 배포해 시크릿을 안전하게 유지합니다. 또한 노드 자체적으로 시크릿을 항상 메모리에만 저장되게 하고 물리 저장소에 기록되지 않도록 합니다. 물리 저장소는 시크릿을 삭제한 후에도 디스크를 완전히 삭제(wiping)하는 작업이 필요하기 때문입니다.

 

마스터 노드(구체적으로 etcd)에는 시크릿을 암호화되지 않은 형식으로 저장하므로, 시크릿에 저장한 민감한 데이터를 보호하려면 마스터 노드를 보호하는 것이 필요합니다. 이렇게 보호할 대상에는 etcd 저장소를 안전하게 하는 것뿐만 아니라 권한 없는 사용자가 API 서버를 이용하지 못하게 하는 것도 포함됩니다.

 

파드를 만들 수 있는 사람은 누구나 시크릿을 파드에 마운트하고 민감한 데이터에 접근하는 것이 가능하기 때문입니다. 쿠버네티스 1.7 부터는 etcd가 시크릿을 암호화된 형태로 저장해 시스템을 좀 더 안전하게 만듭니다. 따라서 언제 시크릿을 사용할지, 컨피그맵을 사용할지 올바르게 선택하는 것이 필요합니다. 둘 중 어느 것을 사용할지 선택할지 다음을 참고하면 좋습니다.

 

  • 민감하지 않고, 일반 설정 데이터는 컨피그맵을 사용합니다.
  • 본질적으로 민감한 데이터는 시크릿을 사용해 키 아래에 보관하는 것이 필요합니다. 만약 설정 파일이 민감한 데이터와 그렇지 않은 데이터를 모두 가지고 있습니다면 해당 파일을 시크릿 안에 저장해야 합니다.

 

이전 포스팅에 인그레스 리소스를 생성할 때, TLS 인증서를 저장하면서 이미 시크릿을 사용 했습니다. 이제 시크릿을 자세히 살펴봅시다.


기본 토큰 시크릿

모든 실행 컨테이너가 마운트해서 갖고 있는 시크릿을 살펴보면서 한번 알아봅시다. 아마도 파드에 대해 kubectl describe 명령어를 사용할 때 본 적이 있을 것입니다. 명령 결과는 항상 다음과 비슷한 내용이 포함돼 있습니다.

Volumes:
  default-token-cfee9:
    Type:               Secret (a volume populated by a Secret)
    SecretName:   default-token-cfee9

모든 파드에는 secret 볼륨이 자동으로 연결돼 있습니다. 이전 kubectl describe 명령어의 출력은 default-token-cfee9라는 시크릿을 참조합니다. 시크릿은 리소스이기 때문에 kubectl get secrets 명령어로 목록을 조회하고 거기서 default-token 시크릿을 찾을 수 있습니다. 다음 결과를 봅시다.

$ kubectl get secrets
NAME                        TYPE                                  DATE         AGE
default-token-cfee9   kubernetes.io/service...       3                39d

kubectl describe 명령어를 사용해 좀 더 자세히 살펴볼 수 있습니다. 다음 예제를 살펴봅시다.

$ kubectl describe secrets
Name: default-token-cfee9
Namespace: default
Labels: <none>
Annotations: kubernetes.io/service-account.name=default
                     kubernetes.io/service-account.uid=cc84bb39-b53f-42010af00237
Type:            kubernetes.io/service-account-token
 
Data
===
ca.crt:              1139 bytes             <  이 시크릿은 세 가지 항목을 갖고 있음을 알 수 있습니다.
namespace:    7 bytes
token:              eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...

시크릿이 갖고 있는 세 가지 항목(ca.crt, namespace, token)은 파드 안에서 쿠버네티스 API 서버와 통신할 때 필요한 모든 것을 나타냅니다. 이상적으로는 애플리케이션이 완전히 쿠버네티스를 인지하지 않도록 하고 싶지만, 쿠버네티스와 직접 대화하는 방법 외에 다른 대안이 없으면 secret 볼륨을 통해 제공된 파일을 사용합니다.

 

kubectl describe pod 명령어는 secret 볼륨이 마운트된 것을 보여줍니다.

Mounts:
    /var/run/secrets/kubernetes.io/serviceaccount from default-token-cfee9

기본적으로 default-token 시크릿은 모든 컨테이너에 마운트되지만, 파드 스펙 안에 auto 사용자가 AP mountService-AccountToken 필드 값을 false로 지정하거나 파드가 사용하는 서비스 어카운트를 false로 지정해 비활성화할 수 있습니다. 이미 얘기한 것처럼 시크릿은 컨피그맵과 비슷하기 때문에 secret 볼륨이 마운트된 디렉터리에서 세 개의 파일을 볼 수 있을 것이라 예상할 수 있습니다. kubectl exec 명령어로 간단하게 확인할 수 있습니다.

$ kubectl exec mypod ls /var/run/secrets/kubernetes.io/serviceaccount/ jul 19A
ca.crt
namespace
token

시크릿 생성

이제 시크릿을 만드는 방법에 대해 다루겠습니다. fortune-serving Nginx 컨테이너가 HTTPS 트래픽을 제공 할 수 있도록 개선하는 것이 목표입니다. 이를 위해 인증서와 개인 키를 만들어야 합니다. 개인 키는 안전하 게 유지해야 하므로 개인 키와 인증서를 시크릿에 넣습니다.

 

먼저 인증서와 개인 키 파일을 만들자(로컬 시스템에서 수행). 이 책 코드 아카이브에 있는 파일을 사용할 수도 있습니다(인증서와 키 파일은 fortune-https 디렉터리에 있습니다).

$ openssl genrsa-out https.key 2048
$ openssl req -new-x509 -key https.key -out https.cert -days 3650 -subje
          /CN=www.kubia-example.com

시크릿에 대해 몇 가지 사항을 잘 설명하기 위해 foo라는 추가 더미 파일을 만들고 그 안에 bar라는 문자열을 저장하자. 잠시 뒤 왜 이런 작업이 필요한지 이해할 수 있습니다.

 

$ echo bar > foo

 

이제 kubectl create secret 명령으로 세 가지 파일에서 시크릿을 만들 수 있습니다.

 

$ kubectl create secret generic fortune-https --from-file=https.key --from-file=https.cert --from-file=foo

secret "fortune-https" created

 

컨피그맵을 작성하는 것과 크게 다르지 않습니다. 여기에서 fortune-https 이름을 가진 generic 시크릿을 생성했습니다. 이 시크릿은 두 가지 항목을 갖고 있습니다(https.key 파일 내용을 가 wano tag dul & 진 https.key 항목 그리고 비슷하게 https.cert 키와 파일). 앞에서 배운 대로 --from-file=fortune -https 옵션을 이용해 개별 파일을 지정하는 대신 디렉터리 전체를 포함할 수 있습니다.

 

  • 시크릿의 세 가지 유형
    • 도커 레지스트리 사용
      • docker-registry
    • TLS 통신
      • tls
      • generic

컨피그맵과 시크릿 비교

시크릿과 컨피그맵은 매우 큰 차이가 있습니다. 이는 쿠버네티스 개발자들이 시크릿을 지원하 다가 컨피그맵을 생성하게 한 이유입니다. 다음 예제는 위에서 생성한 시크릿의 YAML 내용을 보여줍니다.

$ kubectl get secret fortune-https -o yaml
apiVersion: v1
data:
    foo: YmFyCg==
    https.cert: LSOtLS1CRUdJTiBDRVJUSUZIQOFURSOtLSOtCk1JSURCekNDQ...
    https.key: LSOtLS1CRUdJTiBSUOEgUFJJVKFURSBLRVktLSOtLQpNSUlFCE…
kind: Secret
...

이를 이전에 작성한 컨피그맵의 YAML과 비교해봅시다. 다음 예제를 참고 해봅시다.

$ kubectl get configmap fortune-config -o yaml

apiVersion: v1
data:
  my-nginx-config.conf: |
    server {

    }
  sleep-interval: |
     25
kind: ConfigMap

 시크릿 항목의 내용은 Base64 인코딩 문자열로 표시되고, 컨피그맵의 내용은 일반 텍스트로 표시됩니다. 처음에는 시크릿 안에 있는 YAML과 JSON 매니페스트를 다루는 것이 고통스러울 것입니다. 각 항목을 설정하고 읽을 때마다 인코딩과 디코딩을 해야 하기 때문입니다. 

 

Base64 인코딩을 사용하는 까닭은 간단합니다. 시크릿 항목에 일반 텍스트뿐만 아니라 바이너리(binary) 값도 담을 수 있기 때문입니다. Base64 인코딩은 바이너리 데이터를 일반 텍스 트 형식인 YAML이나 JSON 안에 넣을 수 있습니다. 

 

  • 시크릿을 민감하지 않은 데이터도 사용할 수 있지만, 시크릿의 최대 크기는 1MB로 제한됩니다.

stringData 필드

모든 민감한 데이터가 바이너리 형태는 아니기 때문에, 쿠버네티스는 시크릿의 값을 stringData 필드로 설정할 수 있게 해줍니다. 다음 예제에서 사용 방법을 볼 수 있습니다.

kind: Secret
apiVersion: v1
stringData:              >  stringData는 바이너리 데이터가 아닌 시크릿 데이터에 사용할 수 있습니다.
  foo: plain text        >  "plain text"는 Base64로 인코딩되지 않는 것을 볼 수 있습니다.
data:
  https.cert: LSOtLS1CRUdJTiBDRVJUSUZJQOFURSOtLSOtCk1JSURCEKNDQ...
  https.key: LSOtLS1CRUdJTiBSUOEgUFJJVKFURSBLRVktLSOtLQpNSUIFCE...

stringData 필드는 쓰기 전용입니다(읽기 전용이 아닌 쓰기 전용). 즉, 값을 설정할 때만 사용 할 수 있습니다. kubectl get -o yaml 명령으로 시크릿의 YAML 정의를 가져올 때, stringData 필드는 표시되지 않습니다. 대신 stringData 필드(앞의 예제에서 foo 항목)로 지정한 모든 항목은 data 항목 아래에 다른 모든 항목처럼 Base64로 인코딩돼 표시됩니다.


파드에서 시크릿 항목 읽기

secret 볼륨을 통해 시크릿을 컨테이너에 노출하면, 시크릿 항목의 값이 일반 텍스트인지 바이너리 데이터인지에 관계없이 실제 형식으로 디코딩돼 파일에 기록됩니다. 환경변수로 시크릿 항목을 노출할 때도 마찬가지다. 두 경우 모두 애플리케이션에서 디코딩할 필요는 없이 파일 내용을 읽거나 환경변숫값을 찾아 직접 사용할 수 있습니다.


파드에서 시크릿 사용

인증서와 키 파일을 모두 포함하는 fortune-https 시크릿을 Nginx에서 사용할 수 있도록 설정하는 것이 필요합니다.


1. HTTPS를 활성화하도록 fortune-config 컨피그맵 수정

 

 먼저 컨피그맵을 편집해 설정 파일을 다시 수정합니다.

 

$ kubectl edit configmap fortune-config

 

텍스트 편집기가 열리면, my-nginx-config.conf 항목을 다음 예제처럼 수정합니다.

data:
  my-nginx-config.conf: |
    server {
      listen                     80;
      listen                     443 ssl;
      server_name         www.kubia-example.com;
      ssl_certificate          certs/https.cert;                        >  각 경로는 /etc/nginx를 기준으로 지정
      ssl_certificate_key  certs/https.key;
      ssl_protocols         TLSV1 TLSV1.1 TLSV1.2;
      ssl_ciphers            HIGH:!aNULL:!MD5;
      location / {
        root     /usr/share/nginx/html;
        index  index.html index.htm;
       }
    }
  sleep-interval: |

 설정에서 서버가 인증서와 키 파일을 /etc/nginx/certs 경로에서 읽도록 지정했기 때 문에 secret 볼륨을 해당 위치에 마운트하는 것이 필요합니다.


2. fortune-https 시크릿을 파드에 마운트

 

다음으로 새로운 fortune-https 파드를 만들고 다음 예제를 참고해 인증서와 키를 가지고 있는 secret 볼륨을 web-server 컨테이너 안에 적당한 위치에 마운트합니다.

apiVersion: v1
kind: Pod
metadata:
  name: fortune-https
spec:
  containers:
    - image: luksa/fortune:env
      name: html-generator
      env:
        - name: INTERVAL
          valueFrom:
            configMapKeyRef:
              name: fortune-config
              key: sleep-interval
      volumeMounts:
        - name: html
          mountPath: /var/htdocs
    - image: nginx:alpine
      name: web-server
      volumeMounts:
        - name: html
          mountPath: /usr/share/nginx/html
          readOnly: true
        - name: config
          mountPath: /etc/nginx/conf.d
          readOnly: true
        - name: certs                                >  Nginx 서버가 인증서와 키를 /etc/nginx/certs에서요는 읽도록 설정했기 
          mountPath: /etc/nginx/certs/          때문에 시크릿 볼륨을 해당 위치에 마운트합니다.
          readOnly: true
      ports:
        - containerPort: 80
        - containerPort: 443
  volumes:
    - name: html
      emptyDir: {}
    - name: config
      configMap:
        name: fortune-config
        items:
          - key: my-nginx-config.conf
            path: https.conf
    - name: certs                                      >  fortune-https 시크릿을 참조하도록 시크릿 볼륨을 정의합니다.
      secret:
        secretName: fortune-https
  • 컨피그맵 볼륨과 마찬가지로 secret 볼륨 또한 defaultMode 속성을 통해 볼륨에 노출된 파일 권한을 지정할 수 있는 기능을 지원합니다.

3. Nginx가 시크릿의 인증서와 키를 사용하는지 테스트

 

파드가 실행되면 포트 포워드 터널링으로 파드의 443번 포트로 열고 curl 명령으로 요청 을 보내 HTTPS 트래픽을 제공하는지 확인할 수 있습니다.

 

$ kubectl port-forward fortune-https 8443:443 &

Forwarding from 127.0.0.1:8443 -> 443

Forwarding from [::1]:8443 -> 443

$ curl https://localhost:8443 -k

 

서버를 올바르게 설정했습니다면 응답을 얻을 수 있을 것입니다. 서버의 인증서를 앞에서 생성한 인증서와 일치하는지 확인할 수 있습니다. curl 명령을 -v 옵션을 켜고 실행해 상세 로깅을 설정 하면 다음과 같은 결과를 볼 수 있습니다.

$ curl https://localhost:8443 -k -v
* About to connect() to localhost port 8443 (#0)
*        Trying ::1...
* Connected to localhost (::1) port 8443 (#0)
* Initializing NSS with certpath: sql:/etc/pki/nssdb
* skipping SSL peer certificate verification
* SSL connection using TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
* Server certificate:
*         subject: CN=www.kubia-example.com
*         start date: aug 16 18:43:13 2016 GMT             >  서버 인증서는 앞에서 생성해 시크릿에 저장한 인증서와 
*         expire date: aug 14 18:43:13 2026 GMT              일치합니다.
*         common name: www.kubia-example.com
*         issuer: CN=www.kubia-example.com SAT CA

4. 시크릿 볼륨을 메모리에 저장하는 이유

인증서와 개인 키를 secret 볼륨에 마운트해 파드에 성공적으로 전달했습니다. secret 볼륨은 시크릿 파일을 저장하는 데 인메모리 파일시스템(tmpfs)을 사용합니다. 컨테이너에 마운트된 볼륨을 조회하면 이를 볼 수 있습니다.

 

$ kubectl exec fortune-https -c web-server-mount grep certs

tmpfs on /etc/nginx/certs type tmpfs (ro,relatime)

 

tmpfs를 사용하는 이유는 민감한 데이터를 노출시킬 수도 있는 디스크에 저장하지 않기 위해서다.


6. 환경변수로 시크릿 항목 노출

볼륨을 사용하는 대신 컨피그맵에서 sleep-interval 항목을 노출한 것처럼, 시크릿의 개별 항목을 환경변수로 노출할 수 있습니다. 예를 들어 시크릿에서 foo 키를 환경변수 FOO_ SECRET으로 노출하고자 한다면 다음 예제 코드 조각을 컨테이너 정의에 추가합니다.

env:
- name: FOO_SECRET
  valueFrom:                      >  변수는 시크릿 항목에서 설정됩니다.
    secretKeyRef:
      name: fortune-https    >  키를 갖고 있는 시크릿의 이름
      key: foo                       >  노출할 시크릿의 키 이름

이것은 INTERVAL 환경변수를 설정하는 것과 거의 비슷하지만 이번에는 컨피그맵을 참조하는 데 configMapKeyRef 대신 secretKeyRef를 사용해 시크릿을 참조한다는 점이 다릅니다.

 

쿠버네티스에서 시크릿을 환경변수로 노출할 수 있게 해주지만, 이 기능을 사용하는 것이 가장 좋은 방법은 아닙니다. 애플리케이션은 일반적으로 오류 보고서에 환경변수를 기 록하거나 시작하면서 로그에 환경변수를 남겨 의도치 않게 시크릿을 노출할 수 있습니다.

 

또한 자식 프로세스는 상위 프로세스의 모든 환경변수를 상속받는데, 만약 애플리케이션이 타 사(third-party) 바이너리를 실행할 경우 시크릿 데이터를 어떻게 사용하는지 알 수 있는 방법이 없습니다. 환경변수로 시크릿을 컨테이너에 전달하는 것은 의도치 않게 노출될 수 있기 때문에 심사숙고 해서 사용해야 합니다. 안전을 위해서는 시크릿을 노출할 때 항상 secret 볼륨을 사용합니다.


이미지를 가져올 때 사용하는 시크릿

지금까지 애플리케이션에 시크릿을 전달하고 그 안에 있는 데이터를 사용하는 방법을 다뤘습니다. 하지만 쿠버네티스에서 자격증명을 전달하는 것이 필요할 때가 있습니다(예를 들어 프라이빗 컨테이너 이미지 레지스트리를 사용하려는 경우). 이때에도 시크릿을 통해 이뤄진다.

 

지금까지 사용한 모든 이미지는 공개 이미지 레지스트리에 저장돼 있었기 때문에 이미지를 가져오는 데 특별한 자격증명을 필요로 하지 않았다. 하지만 대부분의 조직은 자신들 의 이미지를 모든 사람들이 사용하는 것을 원하지는 않기 때문에 프라이빗 이미지 레지스트리를 사용합니다. 파드를 배포할 때 컨테이너 이미지가 프라이빗 레지스트리 안에 있다면, 쿠버네티스는 이미지를 가져오기 위해 필요한 자격증명을 알아야 합니다. 이를 어떻게 할 수 있는지 살펴봅시다.

 

1. 도커 허브에서 프라이빗 이미지 사용

도커 허브는 공용 이미지 레지스트리 외에도 프라이빗 레지스트리를 만들 수 있게 해줍니다. 웹 브라우저로 http://hub.docker.com에 로그인한 뒤 원하는 저장소를 찾아 프라이빗용으로 표시할 수 있습니다.

 

프라이빗 저장소를 사용하는 파드를 실행하려면 다음 두 가지 작업이 필요합니다.

  • 도커 레지스트리 자격증명을 가진 시크릿 생성
  • 파드 매니페스트 안에 imagePullSecrets 필드에 해당 시크릿 참조

2. 도커 레지스트리 인증을 위한 시크릿 생성

도커 레지스트리 인증에 필요한 자격증명을 저장하는 시크릿을 생성하는 것은 위 시크릿 부분에서 작성한 generic 시크릿과 다르지 않습니다. 하지만 동일한 kubectl create secret 명령을 사용하더라도 유형과 옵션이 다릅니다.

 

$ kubectl create secret docker-registry mydockerhubsecret --docker-username=myusername --docker-password=mypassword --docker-email=my.email@provider.com

 

generic 시크릿을 생성하는 것과 다르게, docker-registry 형식을 가진 mydockerhub secret이라는 시크릿을 만듭니다. 여기에 사용할 도커 허브 사용자 이름, 패스워드, 이메일을 지정합니다. kubectl describe 명령으로 새로 생성한 시크릿을 살펴보면.dockercfg 항 목을 갖고 있는 것을 볼 수 있습니다. 이는 홈 디렉터리에 docker login 명령을 실행할 때 생 성된 .dockercfg 파일과 동일합니다.


3. 파드 정의에서 도커 레지스트리 시크릿 사용

쿠버네티스가 프라이빗 도커 허브 저장소에서 이미지를 가져올 때 시크릿을 사용하려면 다음 예제에 나온 것처럼 시크릿의 이름을 지정하는 것이 필요합니다.

apiVersion: v1
kind: Pod
metadata:
  name: private-pod
spec:
  imagePullSecrets:                     >  프라이빗 이미지 레지스트리에서 이미지를 가져올 수 있도록 설정
  - name: mydockerhubsecret
  containers:
  - image: username/private:tag
    name: main

앞 예제 파드 정의에 보면 mydockerhubsecret 시크릿을 imagePullSecrets 항목으로 지정합니다. 쿠버네비스 사람들이 일반적으로 여러 다양한 파드를 시스템에서 실행하는 것을 감안하면, 모든 파드에 이미지를 가져올 때 사용할 시크릿을 지정하는 것이 필요한지 궁금할 것입니다. 다행히 그렇지는 않습니다.

 

반응형
반응형

목차

  1. 컨피그맵 볼륨을 사용해 컨피그맵 항목을 파일로 노출
    1. 컨피그맵 생성
    2. 볼륨 안에 있는 컨피그맵 항목 사용
    3. Nginx 서버가 마운트한 설정 파일을 사용하는지 확인
    4. 마운트된 컨피그맵 볼륨 내용 살펴보기
    5. 볼륨에 특정 컨피그맵 항목 노출
    6. 디렉터리를 마운트할 때 디렉터리의 기존 파일을 숨기는 것 이해
    7. 디렉터리 안에 다른 파일을 숨기지 않고 개별 컨피그맵 항목을 파일로 마운트
    8. 컨피그맵 볼륨 안에 있는 파일 권한 설정
  2. 애플리케이션 재시작 없이 애플리케이션 설정 업데이트
    1. 컨피그맵 편집
    2. 설정을 다시 로드하기 위해 Nginx에 신호 전달
    3. 파일이 한꺼번에 업데이트되는 원리
    4. 주의사항
    5. 컨테이너 불변성을 우회해도 될까?

컨피그맵 볼륨을 사용해 컨피그맵 항목을 파일로 노출

환경변수 또는 명령줄 인자로 설정 옵션을 전달하는 것은 일반적으로 짧은 변숫값에 대해 서 사용됩니다. 앞에서 본 것처럼 컨피그맵은 모든 설정 파일을 포함할 수 있습니다. 이 파일들을 컨테이너에 노출시키려면, 6장에서 언급한 특수 볼륨 유형 중 하나인 컨피그맵 볼륨을 사용할 수 있습니다.

 

컨피그맵 볼륨은 파일로 컨피그맵의 각 항목을 노출합니다. 컨테이너에서 실행 중인 프로세스는 이 파일 내용을 읽어 각 항목의 값을 얻을 수 있습니다. 이는 대부분 대형 설정 파일들을 컨테이너에 전달하기 위한 방법이지만, 짧은 단일 값 을 전달할 때도 문제없습니다.


컨피그맵 생성

 fortuneloop.sh 스크립트를 수정하는 대신 다른 예를 시도해봅시다. fortune 파드의 웹 서 버 컨테이너 안에서 실행되는 Nginx 웹 서버의 환경 설정을 위해 설정 파일을 사용할 것 입니다. Nginx 서버가 클라이언트로 응답을 압축해서 보내려고 한다고 가정해봅시다. 압축을 사용하도록 설정하려면 Nginx 설정 파일에 다음 예제와 같은 내용이 포함돼야 합니다.

server {
listen                           80;
server_name               www.kubia-example.com;
 
gzip on;                                >   일반 텍스트와 XML 파일에 대해 gzip 압축 활성화
gzip_types text/plain application/xml;
 
location / {
    root     /usr/share/nginx/html;
    index   index.html index.htm;
  }
}

이제 kubectl delete configmap fortune-config 명령으로 기존 fortune-config 컨피그맵을 삭제하고 Nginx 설정 파일을 포함하는 새로운 컨피그맵으로 교체할 수 있습니다. 로컬 디스크에 저장된 파일을 이용해 컨피그맵을 생성해봅시다.

 

configmap-files라는 새 디렉터리를 생성하고 앞 예제 Nginx 설정 파일을 configmap -files/my-nginx-config.conf 파일로 저장합니다. 컨피그맵에 sleep-interval 항목도 포 함시키려면, 동일한 디렉터리에 sleep-interval이라는 일반 텍스트 파일을 생성하고 25를 저장합니다. 이제 디렉터리 안에 있는 모든 파일을 이용해 컨피그맵을 생성해봅시다.

 

$ kubectl create configmap fortune-config --from-file=configmap-files

configmap "fortune-config" create

 

다음 예제는 이 컨피그맵의 YAML 정의를 보여줍니다.

$ kubectl get configmap fortune-config -o yaml

apiVersion: v1
data:
  my-nginx-config.conf:  |           Nginx 설정 파일 내용을 담고 있는 항목
    server {
      listen                           80;
      server_name              www.kubia-example.com;
 
      gzip on;
      gzip_types text/plain application/xml;
 
      location / {
        root     /usr/share/nginx/html;
        index   index.html index.htm;
       } 
    }
  sleep-interval: |                            sleep-interval 항목
     25 
kind: ConfigMap            
...

 두 항목 가운데 첫 번째 줄에서 콜론 뒤 파이프라인(|) 문자는 여러 줄의 문자열이 이어진다는 것을 나타냅니다. 컨피그맵은 두 항목을 포함하며 각 키는 해당 항목을 생성한 파일 이름으로 돼 있습니다. 이제 이 컨피그맵을 파드의 두 컨테이너에서 사용합니다.


볼륨 안에 있는 컨피그맵 항목 사용

컨피그맵의 내용을 가진 볼륨을 생성하는 것은, 컨피그맵 이름으로 참조하는 볼륨을 만들 고 이 볼륨을 컨테이너에 마운트하는 만큼 간단합니다. 이미 볼륨을 생성하고 마운트하는 방 법을 배웠으니 컨피그맵 항목에서 생성된 파일로 볼륨을 초기화하는 방법만 배우면 됩니다.

 

Nginx는 /etc/nginx/nginx.conf 파일의 설정을 읽습니다. Nginx 이미지는 기본 설정 옵션을 가진 파일을 이미 포함하며, 이 파일이 가진 기본 옵션을 모두 무시하고 싶지는 않다. 다행히 기본 설정 파일은 /etc/nginx/conf.d/ 디렉터리 안에 있는 모든 .conf 파일을 포함하기 때문에 원하는 설정 파일을 해당 디렉터리에 추가하면 됩니다. 그림 7.9에서 달성 하고자 하는 것을 보여줍니다. 파드 정의는 다음 예제에서 볼 수 있습니다(관련 없는 부분은 생략됐지만, 코드 아카이브에서 전체 파일을 찾을 수 있습니다).

 apiVersion: v1
kind: Pod
metadata:
  name: fortune-configmap-volume
spec:
  containers:
  - image: nginx:alpine
    name: web-server
    volumeMounts:
...
    - name: config
      mountPath: /etc/nginx/conf.d   >  컨피그맵 볼륨을 마운트하는 위치
      readOnly: true

  volumes:
...
  - name: config  
    configMap:                                  >  이 볼륨은 fortune-config 컨피그맵을 참조합니다.
      name: fortune-config

이 파드 정의에는 fortune-config 컨피그맵을 참조하는 볼륨이 포함돼 있습니다. 해당 볼륨을 Nginx에서 사용할 수 있도록/etc/nginx/conf.d 디렉터리로 마운트합니다.


Nginx 서버가 마운트한 설정 파일을 사용하는지 확인

이제 웹 서버는 응답을 압축해서 보내주도록 설정돼 있어야 합니다. 이를 검증하려면 local host:8080 파드의 80번 포트로 전달하도록 연결하고, 다음 예제처럼 curl 명령을 이용해 서버 응답을 확인할 수 있습니다.

$ kubectl port-forward fortune-configmap-volume 8080:80&
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080>80
 
$ curl -H "Accept-Encoding: gzip" -I localhost:8080
HTTP/1.1 200 OK
tale &
Server: nginx/1.11.1
Date: Thu, 18 Aug 2016 11:52:57 GMT
Content-Type: text/html
Last-Modified: Thu, 18 Aug 2016 11:52:55 GMT
Connection: keep-alive
ETag: W/"57b5a197-37" 
Content-Encoding: gzip               >     응답이 압축됐음을 나타냄 응답이 압축됐음을 나타냄

마운트된 컨피그맵 볼륨 내용 살펴보기

 응답을 통해 원하는 것을 달성했음을 확인할 수 있습니다. 이제/etc/nginx/conf.d 디렉터리에 무엇이 있는지 살펴봅시다.

 

$ kubectl exec fortune-configmap-volume -c web-server Is /etc/nginx/conf.d

my-nginx-config.conf

sleep-interval

 

컨피그맵의 두 항목이 모두 디렉터리에 파일로 추가돼 있습니다. sleep-interval 항목은 fortuneloop 컨테이너에서 사용되고 여기에서는 사용되지 않지만, 같이 포함돼 있습니다. 서로 다른 두 개의 컨피그맵을 작성해 하나는 fortuneloop 컨테이너에 사용하고, 나머지 하 나는 web-server 컨테이너에 사용하도록 할 수 있습니다. 그러나 여러 컨피그맵을 동일한 파 드의 컨테이너들을 구성하는 데 사용하는 것은 무언가 잘못된 느낌입니다. 결국 동일한 파드 에 있는 컨테이너들은 컨테이너가 서로 밀접한 관계를 가지고 있어 하나의 유닛으로 설정 돼야 함을 의미합니다.


볼륨에 특정 컨피그맵 항목 노출

다행히 컨피그맵 볼륨을 컨피그맵 항목의 일부만으로 채울 수 있습니다(여기에서는 my-nginx- config.conf 항목만). sleep-interval 항목은 볼륨이 아닌 환경변수로 전달해 fortuneloop 컨테이너에 영향을 주지 않습니다.

컨피그맵 볼륨 안에 파일로 노출될 항목을 정의하려면, 예제 7.16에 표시된 대로 볼륨의 items 속성을 사용합니다.

volumes:
- name: config
  configMap:
    name: fortune-config
    items:                                      >   볼륨에 포함할 항목을 조회해 선택
    - key: my-nginx-config.conf      >   해당 키 아래에 항목 포함
      path: gzip.conf                        >  항목 값이 지정된 파일에 저장

개별 항목을 지정할 때 항목 키와 함께 각 개별 항목의 파일 이름을 설정하는 것이 필 요합니다. 이전 예제를 이용해 파드를 실행하면 /etc/nginx/conf.d 디렉터리는 gzip.conf 파일만 포함하고, 그 밖에 다른 것은 포함하지 않아 멋지고 깨끗하게 유지됩니다.


디렉터리를 마운트할 때 디렉터리의 기존 파일을 숨기는 것 이해

이 시점에서 논의해야 할 중요한 사항이 있습니다. 이 예제와 이전 예제에서 볼륨을 디렉터리 에 마운트했다. 이는 컨테이너 이미지 자체에 있던 /etc/nginx/conf.d 디렉터리 안에 저 장된 파일을 숨겼음을 의미합니다.

 

이는 일반적으로 리눅스에서 파일시스템을 비어 있지 않은 디렉터리에 마운트할 때 발 생합니다. 해당 디렉터리는 마운트한 파일시스템에 있는 파일만 포함하고, 원래 있던 파일은 해당 파일시스템이 마운트돼 있는 동안 접근할 수 없게 됩니다.

 

이번 경우에는 부작용이 크지 않지만, 일반적으로 중요한 파일을 포함하는 /etc 디렉 터리에 볼륨을 마운트한다고 상상해봅시다. /etc 디렉터리에 있어야 하는 모든 원본 파일이 더 이상 존재하지 않기 때문에 전체 컨테이너가 손상될 수 있습니다. 만약 /etc 디렉터리와 같 은 곳에 파일을 추가하는 것이 필요하다면, 이 방법을 사용할 수 없습니다.


디렉터리 안에 다른 파일을 숨기지 않고 개별 컨피그맵 항목을 파일로 마운트

컨피그맵의 항목을 개별 파일로 기존 디렉터리 안에 있는 모든 파일을 숨기지 않고 추가하 는 방법을 궁금해할 것입니다. 전체 볼륨을 마운트하는 대신 volumeMount에 subPath 속성으 로 파일이나 디렉터리 하나를 볼륨에 마운트할 수 있습니다. 그림으로 설명하는 것이 더 쉬울 것입니다.

 

myconfig.conf 파일을 포함하는 컨피그맵 볼륨을 갖고 있고, 이 파일을 /etc 디렉터 리에 someconfig.conf 파일로 추가하려고 합니다. subpath 속성으로 디렉터리에 있는 다른 파일에 영향을 주지 않고 마운트할 수 있습니다. 관련 있는 파드 정의는 예제 7.17에서 볼 수 있습니다.

 

spec:
  containers:
  - image: some/image
    volumeMounts:
     - name: myvolume
       mountPath: /etc/someconfig.conf    >  디렉터리가 아닌 파일을 마운트
       subpath: myconfig.conf                   >  전체 볼륨을 마운트하는 대신 myconfig.conf 항목만 마운트

subpath 속성은 모든 종류의 볼륨을 마운트할 때 사용할 수 있습니다. 전체 볼륨을 마운트하는 대신에 일부만을 마운트할 수 있습니다. 하지만 개별 파일을 마운트하는 이 방법은 파일 업데이트와 관련해 상대적으로 큰 결함을 가지고 있습니다. 이어지는 절에서 이를 알아볼 것이 다. 그 전에 먼저 파일 권한에 대해 짧게 이야기하며 컨피그맵 볼륨 초기 상태에 관해 이야 기하는 것으로 이 절을 마무리해봅시다.


컨피그맵 볼륨 안에 있는 파일 권한 설정

기본적으로 컨피그맵 볼륨의 모든 파일 권한은 644(-rw-r-r--)로 설정됩니다. 다음 예제와 같이 볼륨 정의 안에 있는 defaultMode 속성을 설정해 변경할 수 있습니다.

volumes:
- name: config
  configMap:
    name: fortune-config
    defaultMode: "6600"        >  모든 파일 권한을 -rw-rw-----로 설정

컨피그맵은 중요하지 않은 설정 데이터에만 사용해야 하지만 이전 예제처럼 파일을 소유한 사용자와 그룹만 파일을 읽고 쓸 수 있도록 만들 수 있습니다.


애플리케이션 재시작 없이 애플리케이션 설정 업데이트

환경변수 또는 명령줄 인수를 설정 소스로 사용할 때의 단점은 프로세스가 실행되고 있는 동안에 업데이트할 수 없다는 것입니다. 컨피그맵을 사용해 볼륨으로 노출하면 파드를 다시 만들거나 컨테이너를 다시 시작할 필요 없이 설정을 업데이트할 수 있습니다.

 

컨피그맵을 업데이트하면, 이를 참조하는 모든 볼륨의 파일이 업데이트됩니다. 그런 다 음 변경됐음을 감지하고 다시 로드하는 것은 프로세스에 달려 있습니다. 그러나 쿠버네티스는 파일 업데이트 후 컨테이너에 신호를 보내는 것을 지원할 가능성이 높다. 하지만 컨피그맵을 업데이트한 후에 파일이 업데이트되기까지 오랜 시간이 걸릴 수 있습니다(최대 1분까지 걸릴 수 있습니다).


컨피그맵 편집

컨피그맵을 변경하고 파드 안에서 실행 중인 프로세스가 컨피그맵 볼륨에 노출된 파일을 다시 로드하는 방법을 살펴봅시다. 이전 Nginx 설정 파일을 편집해 파드 재시작 없이 Nginx 가 새 설정을 사용하도록 만들자. kubectl edit 명령으로 fortune-config 컨피그맵을 편집해 gzip 압축을 해제해봅시다.

 

$ kubectl edit configmap fortune-config

 

편집기가 열리면 gzip on을 gzip off로 변경하고 파일을 저장한 다음 편집기를 닫습니다. 컨피그맵이 업데이트되면 곧 볼륨의 실제 파일도 업데이트됩니다. 파일 내용을 kubectl exec 명령으로 출력해 확인해볼 수 있습니다.

 

$ kubectl exec fortune-configmap-volume -c web-server

 └>  cat /etc/nginx/conf.d/my-nginx-config.conf

 

업데이트된 내용이 보이지 않으면 기다렸다가 다시 시도해봅시다. 파일이 업데이트되려면 시간이 걸립니다. 결국에는 변경된 설정 파일을 볼 수 있지만, Nginx에는 아무런 영향이 없는 것을 알게 될 것입니다. Nginx는 파일의 변경을 감시하지 않으며 자동으로 다시 로드 하지 않기 때문입니다.


설정을 다시 로드하기 위해 Nginx에 신호 전달

Nginx는 설정 파일을 다시 로드하라는 다음 명령을 실행하기 전까지 응답을 계속 압축합니다.

 

$ kubectl exec fortune-configmap-volume -c web-server-- nginx -s reload

 

이제 curl 명령어를 이용해 서버에 다시 접속하면, 더 이상 응답이 압축되지 않는 것을 볼 수 있습니다(더 이상 Content-Encoding: gzip 헤더를 포함하지 않습니다). 이렇게 컨테이너를 재시작하거나 파드를 재생성하지 않고도 애플리케이션의 설정을 효과적으로 변경할 수 있습니다.


파일이 한꺼번에 업데이트되는 원리

쿠버네티스가 컨피그맵 볼륨에 있는 모든 파일을 업데이트하기 전에 애플리케이션이 설정 파일의 변경 사항을 자체적으로 감지하고 다시 로드할 경우에 어떻게 되는지 궁금해할 수 있습니다. 다행히도 모든 파일이 한 번에 동시에 업데이트되기 때문에 이런 일이 발생할 수 없습니다. 쿠버네티스는 심볼릭 링크를 사용해 이를 수행합니다. 만약에 마운트된 컨피그맵 볼륨의 모든 파일을 조회하면 다음 예제와 같은 내용을 보게 될 것입니다.

$ kubectl exec -it fortune-configmap-volume -c web-server -- 1s -1A /etc/nginx/conf.d
total 4
drwxr-xr-x ... 12:15 ..4984_09_04_12_15_06.865837643
Irwxrwxrwx... 12:15 ..data->..4984_09_04_12_15_06.865837643ex Lasdud
1rwxrwxrwx ... 12:15 my-nginx-config.conf -> ..data/my-nginx-config.conf
1rwxrwxrwx ... 12:15 sleep-interval -> ..data/sleep-interval

보는 것처럼 마운트된 컨피그맵 볼륨 안의 파일은 .. data 디렉터리의 파일을 가리키는 심볼릭 링크입니다...data 디렉터리 또한..4984_09_04_something 디렉터리를 가리키는 심 볼릭 링크다. 컨피그맵이 업데이트되면 쿠버네티스는 이와 같은 새 디렉터리를 생성하고, 모든 파일을 여기에 쓴 다음..data 심볼릭 링크가 새 디렉터리를 가리키도록 해, 모든 파일을 한 번에 효과적으로 변경합니다.


주의사항

한 가지 주의 사항은 컨피그맵 볼륨 업데이트와 관련이 있습니다. 만약 전체 볼륨 대신 단일 파일을 컨테이너에 마운트한 경우 파일이 업데이트되지 않습니다.

 

만일 개별 파일을 추가하고 원본 컨피그맵을 업데이트할 때 파일을 업데이트해야 하는 경우 한 가지 해결 방법은 전체 볼륨을 다른 디렉터리에 마운트한 다음 해당 파일을 가리 키는 심볼릭 링크를 생성하는 것입니다. 컨테이너 이미지에서 심볼릭 링크를 만들거나, 컨테 이너를 시작할 때 심볼릭 링크를 만들 수 있습니다.


컨테이너 불변성을 우회해도 될까?

컨테이너의 가장 중요한 기능은 불변성(immutability)입니다. 즉, 동일한 이미지에서 생성된 여러 실행 컨테이너 간에 차이가 없는지 확인할 수 있습니다. 그렇다면 컨테이너를 실행하는 데 사용되는 컨피그맵을 수정해 이 불변성을 우회하는 것이 잘못된 것일까요?

 

애플리케이션이 설정을 다시 읽는 기능을 지원하지 않는 경우에 심각한 문제가 발생합니다. 이로 인해 서로 다른 설정을 가진 인스턴스가 실행되는 결과를 초래합니다. 컨피그맵을 변경한 이후 생성된 파드는 새로운 설정을 사용하지만 예전 파드는 계속해서 예전 설정을 사용합니다. 그리고 이것은 새로운 파드에만 국한되는 문제가 아닙니다. 파드 컨테이너가 어떠 한 이유로든 다시 시작되면 새로운 프로세스는 새로운 설정을 보게 됩니다. 따라서 애플리케이션이 설정을 자동으로 다시 읽는 기능을 가지고 있지 않다면, 이미 존재하는 컨피그맵을 (파드가 사용되는 동안) 수정하는 것은 좋은 방법이 아닙니다.

 

애플리케이션이 다시 읽기(reloading)를 지원한다면, 컨피그맵을 수정하는 것은 그리 큰 문제는 아니다. 하지만 컨피그맵 볼륨의 파일이 실행 중인 모든 인스턴스에 걸쳐 동기적으 로 업데이트되지 않기 때문에, 개별 파드의 파일이 최대 1분 동안 동기화되지 않은 상태로 있을 수 있음을 알고 있어야 합니다.


이제 쿠버네티스 내용이 중간 정도 진행되었는데 난이도가 많이 어려워졌습니다. 때문에 궁금하신 점은 댓글을 달아주시면 답변해드리도록 하겠습니다. 끝까지 봐주셔서 감사합니다. :)

반응형