Notice
Recent Posts
Recent Comments
Link
«   2026/01   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

DevYGwan

Air-Gapped 온프렘 환경에서 Kubernetes Private Registry 구축 본문

Study/AWS_Service

Air-Gapped 온프렘 환경에서 Kubernetes Private Registry 구축

YGwan 2025. 10. 27. 00:40

 공장 같은 곳에 Onprem 서버를 구축할 때 보면 상당히 고려해야 할 점이 많습니다. 대부분 고객들이 Onprem 환경을 원하는 이유는 기기 데이터를 외부에 노출시키고 싶지 않기 때문입니다. 따라서 대부분 환경이 인터넷 연결이 안되어있는 내부망을 사용합니다. 그러다보니 인터넷이 안돼 인터넷이 필요한 작업들은 할 수가 없습니다.

 또한, 공장 기기들 때문인지 LTE 라우터를 통해 인터넷 연결을 잠시 하려고 해도 인터넷이 생각보다 잘 되지 않았습니다. 실제로 k8s내의 clickhouse 이미지를 pull 받아 파드 하나를 띄우려했는데...  이미지 pull만 30분이 넘게 걸렸습니다. 그 뒤로는 limit이 걸렸는지 그 이후에 인터넷 연결이 매우 느려진 현상을 확인할 수 있었습니다.

 

※  그때 당시 문제 상황

 

 물론 미리 이미지를 서버 내 캐시에 저장해놓고 사용하면 외부에서 이미지를 받아오지 않기 때문에 문제가 안되긴 합니다. 저희는 기본적으로 imagePullPolicy 전략을 IfNotPresent로 설정해놨기 때문입니다. 하지만 준비를 철저히 해 가도 요구사항이나 문제가 발생하는 경우가 종종 있어 그때마다 문제가 되었습니다... (항상 완벽한 건 없는 것 같습니다.) 그리고 나중에 설치해 동작 중인 서버 이미지 업데이트가 필요할 경우에도 이런 상황이면 큰 문제가 될 것입니다. 따라서 궁극적으로 이를 효율적으로 할 수 있는 방법이 필요했습니다.

 


 

기존 방식

  저희의 서버 이미지는 현재 AWS ECR에 올라가 있습니다. 실제로 BE 서비스, FE 서비스 등의 이미지는 AWS ECR에서 관리하고 있고 k8s에 띄워진 해당 서비스 별 파드들의 이미지 경로 또한 AWS ECR 경로를 따르고 있습니다. 그러다보니 이미지 업데이트가 발생하고 Onprem Server에 해당 내용을 반영하려면, 인터넷을 탈 수 밖에 없습니다. 그래서 이러한 구조를 변경해야 합니다.

 

해결 방법 ( 방향성 )

이러한 문제를 해결하기 위해 저희가 생각한 방법은, 현재 사용 중인 Docker Registry인 AWS ECR을 로컬 Registry로 전환하는 것입니다. 이렇게 하면 인터넷 연결 없이도 로컬에 갱신된 이미지를 보관하고, onprem 서버에 해당 이미지를 배포할 수 있습니다. 물론 새로운 이미지를 빌드하기 위해서는 여전히 외부 패키지나 의존성 설치를 위해 인터넷 연결이 필요하긴 하지만, 이는 굳이 공장에서 하지 않아도 되기 때문에 이러한 방법으로 하는 것이 저희의 문제를 해결해 줄 수 있을 것이라고 생각했습니다.

 

그래서 이 문제에 대한 방향성은 local docker registry를 사용해서 인터넷 연결 없이 onprem 서버 버전 업데이트를 하는 방향으로 정했습니다. 그렇다면, 어떻게 저희가 어떻게 했는지 설명드리도록 하겠습니다.
설명드리기에 앞서 저희의 서버 환경은

- Talos OS (1.9.6 version)
- k8s ( 1.3.4 version)

 


 

해결 방법 1 : Local 컴퓨터 안에서 Docker Registry 생성 후 k8s 서버에서 사용

 Docker Registry을 Local에서 생성 후,  Local Docker Registry에 이미지를 푸시합니다. 그리고 연결이 유지된 상태에서, 해당 도커이미지에 푸시된 이미지 경로를 다른 pod에서 사용하게끔 설정하면, 저희의 로컬 컴퓨터에 있는 이미지를 사용해서, pod들이 뜨게 되기 때문에 인터넷이 약한 제한된 상황에서도 사용할 수 있었습니다. 실제로 위의 저 문제를 임시방편으로 해결하기 위해 현장에서 이러한 방법으로 로컬에 이미지를 빌드하고, 별도로 띄운 레지스트리를 통해 운영중인 파드 업데이트를 할 수 있었습니다.

 

이 방법을 사용할때 발생한 이슈 사항들은,

 

Local Docker Registry에 이미지 푸시를 위한 설정

  • 기본적으로 로컬 Registry에 이미지를 푸시하려면 반드시 레지스트리 주소를 포함한 이름으로 태그해야 합니다.
  • 따라서 이미지를 빌드하고 해당 이미지에 맞는 태그를 붙인 후에, push를 해야합니다.
docker pull nginx:latest

docker tag nginx:latest localhost:5000/nginx:latest

docker push localhost:5000/nginx:latest

 

HTTP 이미지 사용을 위한 설정

  • Talos는 k8s의 컨테이너 런타임으로 containerd를 사용 ( K8s에서 Pod를 실제로 실행시키는 역할 -> 컨테이너 런타임)
  • 따라서, containerd가 기본적으로 허용하지 않는 HTTP 이미지를 허용하기 위한 설정 필요
machine:
  registries:
    config:
      https://<로컬 ip>:<docker registry 포트>
        tls:
          insecureSkipVerify: true
    mirrors:
      https://<로컬 ip>:<docker registry 포트>:
        endpoints:
          - https://<로컬 ip>:<docker registry 포트>

 

talos Node IP 고정 필요

  • 원래는 라우터를 통해 talos 서버의 ip을 고정했는데, 서버와 직접 이더넷으로 연결하기 때문에 서버가 해당 talos 서버의 ip을 제대로 인식하지 못하는 문제 발생
  • local 서버가 라우터 역할을 할 수 있게끔 설정 (dnsmasq 사용)
    • dnsmasq는 경량 DNS 포워더(forwarder)이자 DHCP 서버로, 소규모 네트워크에서 널리 사용되는 오픈소스 소프트웨어
    • dnsmasq로 talos 서버의 ip을 할당할 수 있는 라우터 대역대를 맞추고, 해당 서버에 static ip 부여

 

하지만 이 방법은 임시방편이였습니다. 이미지를 계속해서 talos 서버에 저장할 수도 없고, 업데이트 동안 라우터를 잠시 빼놔야하기 때문에 번거로웠습니다. 그동안은 데이터가 유실되는 문제도 있었습니다. (물론 라우터를 통해서 해도 되지만 그건 그것대로 번거로움...)

그래서 저희가 생각한 2번째 방법은 local docker registry가 아닌, k8s 내부에 private registry을 띄우고 그걸 사용하게끔 하는 것이였습니다.

 


해결 방법 2 : Local 컴퓨터 안에서 Docker Registry 생성 후 k8s 서버에서 사용

 k8s 내에서 docker registry을 별도의 pod로 띄웁니다. docker registry가 사용하는 저장소는 minio을 사용했습니다. 백엔드 스토리지로 MinIO를 사용한 이유는, 3노드 분산 환경에서 이미지 데이터의 고가용성과 자동 동기화를 보장하기 위함입니다. MinIO의 오브젝트 스토리지 특성을 활용하면, 한 노드에서 푸시된 이미지가 자동으로 다른 노드에도 복제되어 모든 노드가 동일한 이미지를 참조할 수 있다.
이를 통해 레지스트리 관리 복잡도를 줄이고, 장애 시에도 데이터 일관성을 유지할 수 있었습니다.

 

이 방법을 사용할때 발생한 이슈 사항들은,

 

k8s Docker Registry에 이미지 푸시를 위한 설정 ( + local docker engine 설정 추가)

  • 위의 방법과 같이 이미지 푸시를 위해 레지스트리 주소를 포함한 이름으로 태그를 지정해야 합니다.
  • 추가적으로, local docker engine에 insecure-registries 설정을 해줘야됩니다.

※ local docker engine에서 해당 처리를 해야하는 이유

  local docker registry k8s docker registry
Docker Engine 위치 macOS 안에서 직접 실행 macOS 안의 리눅스 VM 안에서 실행
Registry 위치 같은 Docker 네트워크(로컬 컨테이너) Kubernetes 안에 존재 (port-forward로 연결)
통신 경로 Docker 내부 브리지 → 바로 연결 Docker VM → Mac 호스트 → port-forward → K8s Pod
결과 docker push localhost:5001 바로 성공 docker push localhost:5000 하면 연결 안 됨 (timeout)

예전엔 Docker와 registry가 같은 내부 네트워크라 HTTP로도 안전하게 통신할 수 있었지만, 지금은 Docker Engine이 VM 안에서 외부(맥) 로 나가기 때문에 HTTP(비암호화) 통신을 허용하려면 insecure 설정이 필요합니다.

 

Docker Registry 설정

apiVersion: v1
kind: ConfigMap
metadata:
  name: registry-config
  namespace: docker-registry
data:
  config.yml: |
	//...
    storage:
      s3:
 	// s3 설정..
      redirect:
        disable: true
    http:
      addr: :5000

---

apiVersion: apps/v1
kind: Deployment
metadata:
  name: registry
  namespace: docker-registry
spec:
  replicas: 1
  selector:
    matchLabels:
      app: registry
  template:
    metadata:
      labels:
        app: registry
    spec:
      containers:
	// docker registry 설정
      volumes:
        - name: config
          configMap:
            name: registry-config

---

apiVersion: v1
kind: Service
metadata:
  name: registry
  namespace: docker-registry
spec:
  type: NodePort
  selector:
    app: registry
  ports:
    - port: 5000
      targetPort: 5000
      nodePort: 32000

 

 Registry 설정 때문에 엄청 오랜시간이 걸렸습니다. 생각보다 고려해야 할 것이 많았기 때문입니다. 위의 k8s yaml 파일의 주요 내용은

  • node ip로  registry을 외부 노출
    • 원래는, Service dns로 접근하게 하고 싶었습니다. 하지만 기본적으로 Talos의 containerd는 k8s 밖에 있어 service dns 명으로 접근이 불가능합니다. 물론 talos에서 해당 dns을 인식할 수 있게 하는 방법도 있겠지만, 권장하는 방식은 nodePort로 service를 외부에 노출시키는 방법이였습니다. 그래서 nodePort을 노출했습니다.
  • minio 연결을 위한 설정 추가
    • mino 연결을 위한 storage.s3 설정을 추가했습니다. 기본적인 설정은 간단하게 쉽게 처리했지만 문제가 된 점은 pull 일 때입니다. push는 문제 없게 됐는데, push 후 다른 파드에서 해당 이미지를 pull 받을 때 이미지를 찾지 못하는 에러가 발생했습니다.
    • 이 문제를 해결하기 위해 docker registry에서 redirect.disable : true 로 설정해 해결했습니다.
 redirect.disable: true 옵션은 Docker Registry의 S3 backend 동작 방식과 관련된 설정입니다.
이 옵션은 레지스트리가 클라이언트에게 S3의 오브젝트 URL로 리다이렉트할지 여부를 제어합니다. 

 기본적으로 Docker Registry가 S3를 백엔드로 사용할 때는 클라이언트가 docker pul1 등을 통해 이미지 blob을 요청하면, Registry는 S3의 presigned URL(임시 접근 URL)을 발급하고, 클라이언트를 S3로 직접 리다이렉트 시켜서 blob을 다운로드하게 합니다. 따라서 MinIO나 내부 S3 endpoint를 쓰는 경우, docker registry가 endpoint(minio-tenant-hl.minio.svc.cluster.local:9000)에 직접 접근할 수 없습니다. 따라서 해당 옵션을 true로 설정해 이미지를 찾지 못하는 에러를 해결했습니다.

 

HTTP 이미지 사용을 위한 설정

  • 위와 동일하게 talos level에서 containerd가 HTTP 이미지를 허용하기 위한 설정이 필요합니다. 이때 설정은 로컬 ip가 아닌, k8s의 private registry의 node ip입니다.
machine:
  registries:
    config:
      https://<docker-registry-node-ip>:<docker-registry-포트>
        tls:
          insecureSkipVerify: true
    mirrors:
      https://<docker-registry-node-ip>:<docker-registry-포트>:
        endpoints:
          - https://<docker-registry-node-ip>:<docker-registry-포트>

 

이 방법으로 이미지 자체의 registry을 k8s 내부에서 관리하여 장기적으로 효율적인 이미지 관리 시스템을 구축 할 수 있었습니다.

 


정리

 이번에 온프렘의 air gapped 상태에서 컨테이너 이미지를 관리하기 위한 시스템을 구축했습니다. 원래도 talos OS에서 기본적으로 이미지를 캐싱해놓긴 하지만, 온프렘 특성상 현장에서 문제가 많이 발생할 수 있고 캐싱이 언제까지 되는지도 확실하게 알 수 없었기 때문에 장기적인 관점에서 운영 상 이미지를 구축하는 자체 private registry 설정은 필수적이였습니다. 

 이번에 도입하면서 느낀 점은 확실히 필요한 니즈의 기능이 다른 사람들도 필요할 것 같은 니즈라면, 지원하는 기술은 생각보다 많다는 것을 느꼈습니다.