본문 바로가기

Infrastructure/Kubernetes

[컨테이너] 이미지

이미지

쿠버네티스 공식문서를 확인하며 컨테이너 이미지에 대해서 기억해야 하는 부분을 기록한다.

  • 컨테이너 이미지는 애플리케이션과 모든 소프트웨어 의존성을 캡슐화하는 바이너리 데이터를 나타낸다.
  • 컨테이너 이미지는 독립적으로 실행할 수 있고 런타임 환경에 대해 잘 정의된 가정을 만드는 실행 가능한 소프트웨어 번들이다.
  • 일반적으로 파드에서 참조하기 전에 애플리케이션의 컨테이너 이미지를 생성해서 레지스트리로 푸시한다.

이미지 이름

  • 컨테이너 이미지는 일반적으로 pause, example/mycontainer 또는 kube-apiserver와 같은 이름을 부여하며 이름에는 레지스트리 호스트와 포트 번호까지 포함될 수 있다.
  • 레지스트리 호스트 이름을 지정하지 않으면, 쿠버네티스는 도커 퍼블릭 레지스트리를 의미한다고 가정한다.
  • 이미지 이름 부분 다음에 “tag”를 추가할 수 있고 태그를 사용해서 동일한 시리즈 이미지의 다른 버전을 식별할 수 있다.
  • 이미지 태그는 소문자와 대문자, 숫자, 밑줄(_), 마침표(.), 및 대시(-)로 구성된다. 태그 안에서 구분 문자를 배치할 수 있는 위치에 대한 추가 규칙이 있으며 태그를 지정하지 않으면, 쿠버네티스는 태그 latest를 의미한다고 가정한다.

[정리]

이미지의 이름에는 레지스트리 호스트와 포트 번호까지 포함될 수 있고 호스트 이름이 없는 경우 도커 퍼블릭 레지스트리(도커 허브)에서 이미지를 검색한다. 태그를 통하여 버전을 관리할 수 있으며 영숫자와 특수문자(-,.,-)를 허용한다. 만약 태그가 없다면 latest 태그의 이미지를 찾는다.


이미지 업데이트

  • 디플로이먼트, 스테이트풀셋, 파드 또는 파드 템플릿은 포함하는 다른 오브젝트를 처음 만들 때 특별히 명시하지 않은 경우 기본적으로 해당 파드에 있는 모든 컨테이너의 풀(pull) 정책은 IfNotPresent로 설정되며 이렇게 설정된 경우 이미 존재하는 이미지는 더 이상 풀 하지 않는다.

이미지 풀(pull) 정책

  • 컨테이너에 대한 imagePullPolicy와 이미지의 태그는 kubelet이 특정 이미지를 풀하려고 할 때 영향을 준다.
    • IfNotPresent: 이미지가 로컬에 없는 경우에만 내려받는다.
    • Always: kubelet이 컨테이너를 기동할 때마다, kubelet이 컨테이너 이미지 레지스트리에 이름과 이미지의 다이제스트가 있는지 질의한다. 컨테이너 이미지가 로컬에 있는 경우, kubelet은 캐시된 이미지를 사용한다. 이외의 경우, kubelet은 검색된 다이제스트를 가진 이미지를 내려받아서 컨테이너를 기동할 때 사용한다.
    • Never: kubelet은 이미지를 가져오려고 시도하지 않는다. kubelet은 이미지가 로컬에 존재하는 경우에만 컨테이너를 기동하고 이외의 경우에는 기동에 실패한다.
  • 이미지 제공자에 앞서 깔린 캐시의 의미 체계는 레지스트리에 안정적으로 접근할 수 있다면 imagePullPolicy: always도 효율적이다. 컨테이너 런타임은 노드에 이미 존재하는 이미지 레이어를 알고 다시 내려받지 않는다.
  • 프로덕션 환경에서 컨테이너를 배포하는 경우 어떤 버전이 기동되고 있는지 추적이 어렵기 때문에 :latest 태그 사용을 지양해야 한다.
  • 파드가 항상 컨테이너 이미지의 같은 버전을 사용하는 것을 확실히 하려면, 이미지의 태그가 아니라 다이제스트를 명시하면 된다.
  • 이미지 태그를 사용하는 경우, 이미지 레지스트리에서 한 이미지를 나타내는 태그에 코드를 변경하게 되면 기존 코드와 신규 코드를 구동하는 파드가 섞이게 된다.
  • 이미지 다이제스트를 통해 이미지의 특정 버전을 유일하게 식별할 수 있기 때문에, 쿠버네티스는 매번 해당 이미지 이름과 다이제스트가 명시된 컨테이너를 기동해서 같은 코드를 구동한다.
  • 이미지를 다이제스트로 명시하면 구동할 코드를 고정시켜서 레지스트리에서의 변경으로 인해 버전이 섞이는 일이 발생하지 않도록 해준다.
  • 파드나 파드 템플릿이 생성될 때 구동 중인 워크로드가 태그가 아닌 이미지 다이제스트를 통해 정의되도록 조작해주는 서드-파티 어드미션 컨트롤러가 있다. 이는 레지스트리에서 태그가 변경되는 일이 발생해도 구동 중인 워크로드가 모두 같은 코드를 사용하고 있다는 것을 보장한다.

[정리]

이미지 풀 정책(imagePullPolicy)을 지정하지 않는 경우 기본값은 IfNotPresent이기 때문에 로컬 스토리지에 이미지가 없는 경우에만 풀 한다. 정책이 Always인 경우 로컬에 동일한 다이제스트를 가진 이미지가 없는 경우에만 레지스트리에서 이미지를 풀 한다. Never는 이미지를 풀 하지 않으며 이미지가 없는 경우 오류가 발생한다. 명확한 런타임 이미지를 사용하기 위해서 태그보다는 다이제스트를 사용해야 하며 :latest 태그를 지양해야 한다.

정리하면 다른 코드에 동일한 태그는 사용될 수 있지만 코드가 다르다면 다이제스트가 동일할 수 없다. 즉, 다이제스트 값이 동일하다면 완전하게 동일한 이미지로 볼 수 있다. 이것을 강제하는 서드-파티 어드미션 컨트롤러도 있다.


기본 이미지 풀 정책

  • 사용자 또는 컨트롤러가 신규 파드를 API 서버에 요청할 때, 특정 조건에 부합하면 클러스터가 imagePullPolicy 필드를 설정한다.
    • imagePullPolicy 필드를 생략하고 컨테이너 이미지의 태그가 **:latest 인 경우, imagePullPolicy는 자동으로 Always로 설정**된다.
    • imagePullPolicy 필드를 생략하고 컨테이너 이미지의 태그를 명시하지 않은 경우, imagePullPolicy는 자동으로 Always로 설정된다.
    • imagePullPolicy 필드를 생략하고, 명기한 컨테이너 이미지의 태그가 **:latest가 아니면, imagePullPolicy는 자동으로 IfNotPresent로 설정**된다.
  • 컨테이너의 imagePullPolicy값은 오브젝트가 처음 created 일 때 항상 설정되고 나중에 이미지 태그가 변경되더라도 업데이트 되지 않는다.
  • 태그가 :latest가 아닌 이미지로 디플로이먼트를 생성하고, 나중에 해당 디플로이먼트의 이미지를 :latest태그로 업데이트하면 imagePullPolicy필드가 Always로 변경되지 않는다.
  • 오브젝트를 처음 생성 한 후 모든 오브젝트의 풀 정책을 수동으로 변경해야 한다.

이미지 풀 강제

  • 이미지를 내려받도록 강제하려면, 아래의 방법 중 한가지 방법을 사용한다.
    • 컨테이너의 **imagePullPolicyAlways로 설정**한다.
    • imagePullPolicy를 생략하고 사용할 이미지 태그로 :latest를 사용한다. 그러면 사용자가 파드를 요청할 때 쿠버네티스가 정책을 Always로 설정한다.
    • imagePullPolicy사용할 이미지의 태그를 생략한다. 그러면 사용자가 파드를 요청할 때 쿠버네티스가 정책을 Always로 설정한다.
    • AlwaysPullImages 어드미션 컨트롤러를 활성화한다.

이미지풀백오프(ImagePullBackOff)

  • kubelet이 컨테이너 런타임을 사용하여 파드의 컨테이너를 생성을 시작할 때, **ImagePullBackOff로 인해 컨테이너가 Waiting 상태에 있을 수** 있다.
  • ImagePullBackOff라는 상태는 쿠버네티스가 컨테이너 이미지를 가져올 수 없기 때문에 컨테이너를 실행할 수 없음을 의미한다.
  • BackOff라는 단어는 쿠버네티스가 백오프 딜레이를 증가시키면서 이미지 풀링을 계속 시도할 것임을 나타낸다.
  • 쿠버네티스는 시간 간격을 늘려가면서 시도를 계속하며, 시간 간격의 상한은 쿠버네티스 코드에 5분으로 정해져 있다.

[정리]

사용자 또는 컨트롤러가 신규 파드를 API 서버에 요청할 때 조건에 따라 클러스터가 이미지 풀 정책을 설정한다.

이미지를 항상 내려받도록 강제하기 위한 여러가지 방법이 존재한다.

파드의 상태가 Waiting 이며 ImagePullBackOff로 표시되어 있는 경우 컨테이너 이미지를 가져올 수 없는 상황이마 쿠버네티스는 일정 시간마다 지속적으로 이미지 풀을 시도한다.


이미지 인덱스가 있는 다중 아키텍처 이미지

  • 바이너리 이미지를 제공할 뿐만 아니라, 컨테이너 레지스트리는 컨테이너 이미지 인덱스를 제공할 수도 있다.
  • 이미지 인덱스는 컨테이너의 아키텍처별 버전에 대한 여러 이미지 매니페스트를 가리킬 수 있다.
  • 아이디어는 이미지의 이름(pause, example/mycontainer, kube-apiserver)를 가질 수 있다는 것이다. 때문에 다른 시스템들이 사용하고 있는 컴퓨터 아키텍처에 적합한 바이너리 이미지를 가져올 수 있다.
  • 쿠버네티스 자체는 일반적으로 -$(ARCH) 접미사로 컨테이너 이미지의 이름을 지정한다. 이전 버전과의 호환성을 위해, 접미사가 있는 오래된 이미지를 생성한다.
  • 아이디어는 모든 아키텍처에 대한 매니페스트가 있는 pause 이미지와 이전 구성 또는 이전에 접미사로 이미지를 하드 코딩한 YAML 파일과 호환되는 pause-amd64라고 하는 이미지를 생성한다.

[정리]

여러 아키텍처(OS 예: ARM, AMD)에서 실행되어야 하는 경우 컨테이너의 이미지 인덱스를 사용하면 된다.

이미지 인덱스는 아키텍처별 버전에 대한 여러 이미지 매니페스트를 가리킬 수 있다.

쿠버네티스는 자체적으로 접미사를 사용하여 이미지의 이름을 지정한다.


프라이빗 레지스트리 사용

  • 프라이빗 레지스트리는 해당 레지스트리에서 이미지를 읽을 수 있는 키를 요구하고 자격 증명(credential)은 여러가지 방법으로 제공될 수 있다.
    • 프라이빗 레지스트리에 대한 인증을 위한 노드 구성
      • 모든 파드는 구성된 브라이빗 레지스트리를 읽을 수 있음
      • 클러스터 관리자에 의한 노드 구성 필요
    • 미리 내려받은(pre-pulled) 이미지
      • 모든 파드는 노드에 캐시된 모든 이미지를 사용 가능
      • 셋업을 위해서는 모든 노드에 대해서 root 접근이 필요
    • 파드에 ImagePullSecrets을 명시
      • 자신의 키를 제공하는 파드만 프라이빗 레지스트리에 접근 가능
    • 공급 업체별 또는 로컬 확장
      • 사용자 정의 노드 구성을 사용하는 경우, 사용자(또는 클라우드 제공자)가 컨테이너 레지스트리에 대한 노드 인증 메커니즘을 구현 가능

프라이빗 레지스트리에 인증하도록 노드 구성

  • 크리덴셜 설정에 대한 상세 지침은 사용하는 컨테이너 런타임 및 레지스트리에 따라 다르다.

config.json 파일 해석

  • config.json 파일의 해석에 있어서, 기존 도커의 구현과 쿠버네티스의 구현에 차이가 있다.
  • 도커에서는 auths키에 특정 루트 URL만 기재할 수 있으나, 쿠버네티스에서는 glob URL과 접두사-매칭 경로도 기재할 수 있다. 아래와 같은 config.json도 유효하다는 의미다.
{
    "auths": {
        "*my-registry.io/images": {
            "auth": "…"
        }
    }
}
  • 루트 URL(*my-registry.io)은 아래의 문법을 사용하여 매치된다.
pattern:
    { term }

term:
    '*'         구분자가 아닌 모든 문자와 매치됨
    '?'         구분자가 아닌 문자 1개와 매치됨
    '[' [ '^' ] { character-range } ']'
                문자 클래스 (비어 있으면 안 됨))
    c           문자 c에 매치됨 (c != '*', '?', '\\', '[')
    '\\' c      문자 c에 매치됨

character-range:
    c           문자 c에 매치됨 (c != '\\', '-', ']')
    '\\' c      문자 c에 매치됨
    lo '-' hi   lo <= c <= hi 인 문자 c에 매치됨
  • 이미지 풀 작업 시, 모든 유효한 패턴에 대해 크리덴셜을 CRI 컨테이너 런타임에 제공하며, 아래의 이미지 이름들은 성공적으로 매칭될 것이다.
    • my-registry.io/images
    • my-registry.io/images/my-image
    • my-registry.io/images/another-image
    • sub.my-registry.io/images/my-image
    • a.sub.my-registry.io/images/my-image
  • kubelet은 인식된 모든 크리덴셜을 순차적으로 이용하여 이미지 풀을 수행하기 때문에 아래와 같이 여러 항목을 기재할 수도 있다.
{
    "auths": {
        "my-registry.io/images": {
            "auth": "…"
        },
        "my-registry.io/images/subpath": {
            "auth": "…"
        }
    }
}
  • 컨테이너가 my-registry.io/images/subpath/my-image 이미지를 풀 해야 한다고 명시하면, kubelet은 크리덴셜을 순차적으로 사용하여 풀을 시도한다.

미리 내려받은 이미지

  • 노드의 구성을 제어할 수 있는 경우에만 적합하며, 클라우드 제공자가 노드를 관리하고 자동으로 교체한다면 안정적으로 작동하지 않을 수 있다.
  • kubelet은 지정된 레지스트리에서 각 이미지를 풀하려고 하지만 컨테이너의 imagePullPolicy의 속성이 IfNotPresent 또는 Never로 설정되어 있는 경우 로컬 이미지가 우선적으로 사용된다.
  • 레지스트리 인증의 대안으로 미리 풀 된 이미지에 의존하려면, 클러스터의 모든 노드가 동일하게 미리 내려받은 이미지를 가지고 있는지 확인해야 한다.
  • 이러한 방식의 인증은 이미지를 풀하는 시간을 단축하거나 프라이빗 레지스트리 인증에 대한 대안으로 사용될 수 있다.
  • 파드는 미리 내려받은 이미지에 대한 읽기 접근 권한을 가지게 된다.

파드에 ImagePullSecrets 명시

  • 프라이빗 레지스트리에의 이미지를 기반으로 컨테이너를 실행하는 데 권장되는 방식이다.
  • 쿠버네티스는 파드에 컨테이너 이미지 레지스트리 키를 명시하는 것을 지원한다.

도커 구성으로 시크릿 생성

  • 레지스트리에 인증하기 위해서는, 레지스트리 호스트네임 뿐만 아니라, 사용자 이름, 비밀번호 및 클라이언트 이메일 주소를 알아야 한다. 아래의 커맨드를 입력한다.
$ kubectl create secret docker-registry <name> --docker-server=DOCKER_REGISTRY_SERVER --docker-username=DOCKER_USER --docker-password=DOCKER_PASSWORD --docker-email=DOCKER_EMAIL
  • 만약 도커 자격 증명 파일이 이미 존재한다면, 위의 커맨드를 사용하지 않고 자격 증명 파일을 쿠버네티스 “시크릿”으로 가져올 수 있다.
  • kubectl create secret docker-registry는 하나의 프라이빗 레지스트리에서만 작동하는 시크릿을 생성하기 때문에, 여러 프라이빗 컨테이너 레지스트리를 사용하는 경우 특히 유용하다.
  • 파드는 이미지 풀 시크릿을 자신의 네임스페이스에서만 참조할 수 있다. 이러한 과정은 네임스페이스 당 한 번만 수행될 필요가 있다.

파드의 imagePullSecrets 참조

  • imagePullSecrets 섹션을 파드의 정의에 추가함으로써 해당 시크릿을 참조하는 파드를 생성할 수 있다. 예시는 아래와 같다.
cat <<EOF > pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: foo
  namespace: awesomeapps
spec:
  containers:
    - name: foo
      image: janedoe/awesomeapp:v1
  imagePullSecrets:
    - name: myregistrykey
EOF

cat <<EOF >> ./kustomization.yaml
resources:
- pod.yaml
EOF
  • 프라이빗 레지스트리를 사용하는 각 파드에 대해서 수행될 필요가 있지만, 필드를 셋팅하는 과정은 서비스 어카운트 리소스에 imagePullSecrets을 세팅하여 자동화할 수 있다.
  • 노드 당 .docker/config.json와 함께 사용할 수 있으며 자격 증명은 병합된다.

[정리]

프라이빗 레지스트리에서 이미지를 풀 하기 위해서는 자격증명이 필요하다.

클러스내에 프라이빗 레지스트리가 위치하는 경우 모든 파드는 프라이빗 레지스트리의 이미지를 읽을 수 있다.

미리 내려 받은 이미지가 있는 경우 모든 파드는 동일한 노드에 위치한 이미지를 읽을 수 있다.

파드에 ImagePullSecrets가 명시되어 있는 경우 키를 제공하는 파드만 레지스트리에 접근할 수 있다.

AWS와 같은 클라우드 제공자를 사용하는 경우 그에 따른 인증 메커니즘을 따라야 레지스트리에 접근할 수 있다.


유스케이스

  • 프라이빗 레지스트리를 구성하기 위한 많은 솔루션이 있으며 대표적으로 아래와 같은 유스케이스가 있다.
    1. 오픈소스와 같은 비소유 이미지만 실행하는 클러스터의 경우에는 이미지를 숨길 필요가 없다.
      • 퍼블릭 레지스트리의 퍼블릭 이미지를 사용한다.
        • 설정이 필요없다.
        • 일부 클라우드 제공자는 퍼블릭 이미지를 자동으로 캐시하거나 미러링하므로, 가용성이 향상되고 이미지를 가져오는 시간이 단축된다.
    2. 모든 클러스터 사용자에게는 보이지만, 회사 외부에는 숨겨야 하는 일부 독점 이미지를 실행하는 클러스터의 경우.
      • 호스트된 프라이빗 레지스트리를 사용해야 한다.
        • 프라이빗 레지스트리에 접근해야 하는 노드에 수동 설정이 필요할 수 있다.
      • 방화벽 뒤에서 읽기 접근 권한을 가진 내부 프라이빗 레지스트리를 실행한다.
        • 쿠버네티스 구성은 필요하지 않다.
      • 이미지 접근을 제어하는 호스팅된 컨테이너 이미지 레지스트리 서비스를 사용한다.
        • 수동 노드 구성에 비해서 클러스터 오토스케일링과 더 잘 동작할 것이다.
      • 노드의 구성 변경이 불편한 클러스터에서는 imagePullSecrets를 사용한다.
    3. 독점 이미지를 가진 클러스터로, 그 중 일부가 더 엄격한 접근 제어를 필요로 하는 경우.
      • AlwaysPullImages 어드미션 컨트롤러가 활성화되어 있는지 확인한다. 그렇지 않으면, 모든 파드가 잠재적으로 모든 이미지에 접근 권한을 가진다.
      • 민감한 데이터는 이미지 안에 포장하는 대신, “시크릿”리소스로 이동한다.
    4. 멀티-테넌트 클러스터에서 각 테넌트가 자신의 프라이빗 레지스트리를 필요로 하는 경우.
      • AlwaysPullImages 어드미션 컨트롤러가 활성화되어 있는지 확인한다. 그렇지 않으면, 모든 파드가 잠재적으로 모든 이미지에 접근 권한을 가진다.
      • 인가가 요구되도록 프라이빗 레지스트리를 실행한다.
      • 각 테넌트에 대한 레지스트리 자격 증명을 생성하고, 시크릿에 추가하여 각 테넌트 네임스페이스에 시크릿을 채운다.
      • 테넌트는 해당 시크릿을 각 네임스페이스의 imagePullSecrets에 추가한다.
  • 다중 레지스트리에 접근해야 하는 경우, 각 레지스트리에 대해 하나의 시크릿을 생성할 수 있다.

[정리]

오픈소스와 같은 비소유 이미지는 퍼블릭 레지스트리를 사용한다.

회사 외부에서 접근하면 안되는 이미지의 경우 프라이빗 레지스트리를 사용하여 관리한다.

외부에 노출되면 안되는 정보는 이미지 안에 담지 말고 “시크릿”의 리소스에 추가해야 한다.


참고 자료