Docker & Kubernetes

도커 사설 레지스트리(Docker Private Registry)에 이미지 배포하기

beekei 2022. 12. 29. 18:55
반응형

save나 export와 같은 방법으로 이미지를 단일 파일로 추출해 배포할 수도 있지만 이미지 파일의 크기가 너무 크거나 도커 엔진의 수가 많다면 이미지를 파일로 배포하기 어렵다. 또한 도커의 이미지 구조인 레이어 형태를 이용하지 않으므로 매우 비효율적이다.

 

이를 해결하는 방법은 도커 허브(Docker Hub)를 사용하거나 도커 사설 레지스트리(Docker Private Registry)를 사용하는 방법 등 여러 가지가 있다.

레지스트리 컨테이너 실행

도커 사설 레지스트리를 사용하면 개인 서버에 저장소를 생성할 수 있다.

이 레지스트리는 컨테이너로서 구현되므로 이에 해당하는 도커 이미지가 존재한다.

$ docker run -d --name myregistry -p 5000:5000 --restart=always registry
$ curl localhost:5000/v2/
{}%

레지스트리 컨테이너는 기본적으로 5000번 포트를 사용하며 이 포트로 RESTful API를 사용할 수도 있다.


레지스트리에 이미지 올리기

마찬가지로 docker tag 명령어를 사용해 이미지의 이름을 <레지스트리 호스트>:<포트>/<이미지명>:<태그> 형태로 변경한다.

$ docker tag ubuntu:20.04 localhost:5000/myubuntu:0.1
$ docker images
REPOSITORY                         TAG       IMAGE ID       CREATED         SIZE
localhost:5000/myubuntu            0.1       d5447fc01ae6   2 weeks ago     72.8MB
ubuntu                             20.04     d5447fc01ae6   2 weeks ago     72.8MB

이미지 이름을 변경했다면 docker push 명령어를 사용해 이미지를 올려보자.

$ docker push localhost:5000/myubuntu:0.1
The push refers to repository [localhost:5000/myubuntu]
0002c93bdb37: Pushed
0.1: digest: sha256:8eb87f3d6c9f2feee114ff0eff93ea9dfd20b294df0a0353bd6a4abf403336fe size: 529

 

만약 로컬이 아닌 실제 호스트에서 시도했을때 아래와 같은 문구가 출력된다면 HTTPS 인증서를 적용해야 한다.

The push refers to a repository [{호스트}:{포트}/{이미지명}]
get https://{호스트}/v2/: http: server gave HTTP response to HTTPS client

 

도커 데몬은 HTTPS를 사용하지 않는 레지스트리 컨테이너에 접근하지 못하도록 설정되어 있다.

만약 HTTPS 사용하지 않으려면 아래 옵션을 추가해 도커를 재시작해야 한다.

DOCKER_OPTS="--insecure-registry={호스트}:{포트}"

서버 접근 권한 설정(Nginx)

도커 허브에서 저장소를 사용할 때 로그인을 해야 되는 것처럼 미리 정의된 계정으로 로그인하도록 설정할 수 있다.

하지만 로그인 기능은 보안을 적용하지 않은(insecure) 레지스트리 컨테이너는 사용할 수 없으므로 Self-Signed 인증서와 키를 발급함으로써 TLS을 적용해 별도의 Nginx 서버 컨테이너를 생성해 레지스트리 컨테이너와 연동하는 방식으로 접근 권한을 설정할 수 있다.

 

1. ROOT 인증서(CA) 생성

아래 명령어를 차례대로 입력해 Self-signed ROOT 인증서 파일을 생성해 보자.

$ mkdir certs
$ openssl genrsa -out ./certs/ca.key 2048
Generating RSA private key, 2048 bit long modulus
............+++++
..............................+++++
e is 65537 (0x10001)

$ openssl req -x509 -new -key ./certs/ca.key -days 1000 -out ./certs/ca.crt
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) []:kr
State or Province Name (full name) []:seoul
Locality Name (eg, city) []:gangnam
Organization Name (eg, company) []:devbeekei
Organizational Unit Name (eg, section) []:development
Common Name (eg, fully qualified host name) []:devbksheen.tistory.com
Email Address []:devbeekei.shin@gmail.com

 

2. 컨테이너 인증서 생성

생성한 ROOT 인증서로 레지스트리 컨테이너에 사용될 인증서를 생성해야 한다.

인증서 서명 요청 파일인 CSR(certificate signing request) 파일을 생성하고 ROOT 인증서로 새로운 인증서를 발급한다.

$ openssl genrsa -out ./certs/domain.key 2048
Generating RSA private key, 2048 bit long modulus
....+++++
...........................+++++
e is 65537 (0x10001)

$ openssl req -new -key ./certs/domain.key -subj /CN=127.0.0.1 -out ./certs/domain.csr
$ echo subjectAltName=IP:127.0.0.1 > extfile.cnf
$ openssl x509 -req -in ./certs/domain.csr -CA ./certs/ca.crt -CAkey ./certs/ca.key -CAcreateserial -out ./certs/domain.crt -days 10000 -extfile extfile.cnf
Signature ok
subject=/CN=127.0.0.1
Getting CA Private Key

실제 호스트에서 시도할 때는 127.0.0.1 대신 호스트 서버의 IP나 도메인을 입력해야 한다.

이 IP로 Nginx 서버 컨테이너에 접근할 것이다.

 

이제 레지스트리에 로그인할 때 사용할 계정과 비밀번호를 저장하는 파일을 생성해야 한다.

$ htpasswd -c htpasswd devbeekei
New password:
Re-type new password:
Adding password for user devbeekei

$ mv htpasswd certs/

devbeekei라는 계정을 사용하도록 설정하였다.

htpasswd가 설치돼 있지 않다면 아래 명령어로 설치해야 한다.
데비안 계열(Ubuntu) - apt-get install apache2-utils
레드헷 계열(CentOS) - yum install httpd-tools

 

3. Nginx 컨테이너 생성

다음은 certs 디렉터리에 nginx.conf 파일을 저장한다.

$ vim certs/nginx.conf
upstream docker-registry {
        server registry:5000;
}

server {
        listen 443;
        server_name     127.0.0.1;
        ssl on;
        ssl_certificate /etc/nginx/conf.d/domain.crt;
        ssl_certificate_key /etc/nginx/conf.d/domain.key;
        client_max_body_size 0;
        chunked_transfer_encoding on;

        location /v2/ {
                if ($http_user_agent ~ "^(docker\/1\.(3|4|5(?!\.[0-9]-dev))|Go).*$") {
                        return 404;
                }

                auth_basic "registry.localhost";
                auth_basic_user_file /etc/nginx/conf.d/htpasswd;
                add_header 'Docker-Distribution-Api-Version' 'registry/2.0' always;

                proxy_pass                                      http://docker-registry;
                proxy_set_header        HOST                    $http_host;
                proxy_set_header        X-Real-IP               $remote_addr;
                proxy_set_header        X-Forwarded-For         $proxy_add_x_forwarded_for;
                proxy_set_header        X-Forwarded-Proto       $scheme;
                proxy_read_timeout                              900;
        }
}

마찬가지로 127.0.0.1 대신 실제 호스트 IP나 도메인을 입력하면 된다.

Nginx 서버에서 SSL 인증에 필요한 각종 파일의 위치와 레지스트리 컨테이너로의 프락시(Proxy)를 설정한다.

 

기존에 생성한 레지스트리 컨테이너가 있다면 혼동을 피하고자 삭제하고 다시 생성한다.

$ docker run -d --name myregistry --restart=always registry

Nginx를 통해 레지스트리에 접근할 것이므로 포트 포워딩을 할 필요가 없다.

레지스트리 컨테이너가 정상적으로 작동한다면 Nginx 컨테이너를 생성한다.

$ docker run -d --name nginx -p 443:443 
--link myregistry:registry \ 
-v $(pwd)/certs/:/etc/nginx/conf.d \
nginx

$ docker ps
CONTAINER ID   IMAGE      COMMAND                  CREATED          STATUS          PORTS                          NAMES
01de5f3e5793   nginx      "/docker-entrypoint.…"   11 minutes ago   Up 10 minutes   80/tcp, 0.0.0.0:443->443/tcp   nginx
3b3847a9acfe   registry   "/entrypoint.sh /etc…"   14 minutes ago   Up 14 minutes   5000/tcp                       myregistry

위에서 생성한 nginx.conf, domain.crt, docker.key 파일이 존재하는 auth 디렉터리를 -v 옵션으로 컨테이너에 공유한다.

 

5. 도커 로그인 후 이미지 PUSH

이제 docker login 명령어를 사용해서 로그인하면 된다.

$ docker login https://127.0.0.1
Username: devbeekei
Password:
Login Succeeded

 

127.0.0.1에서는 잘 작동하지만 실제 호스트에서 시도해보면 아래 문구가 출력될 것이다.

Error response from daemon: Get https://{호스트}/v1/users/: x509: certificate signed by unknown authority

 

신뢰할 수 없는 인증서인 Self-signed 인증서를 사용했으므로 도커에서 이를 사용하지 못하도록 에러를 출력하는 것이다.

따라서 우리가 직접 서명한 인증서를 신뢰할 수 있는 인증서 목록에서 추가해야 한다.

# 데비안 계열(Ubuntu)
$ cp certs/ca.crt /usr/local/share/ca-certificates/
$ update-ca-certificates

# 레드헷 계열(CentOS)
$ cp certs/ca.crt /etc/pki/ca-trust/source/anchors/
$ update-ca-trust

$ service docker restart
$ docker start nginx

 

로그인에 성공했다면 이미지를 레지스트리에 Push 해보자.

지금은 5000번 포트 포워딩이 없어졌기 때문에 <호스트>/<이미지명>:<태그> 형태로 이미지명을 변경해서 push 해야 한다.

$ docker tag ubuntu:20.04 127.0.0.1/myubuntu:0.1
$ docker push 127.0.0.1/myubuntu:0.1
The push refers to repository [127.0.0.1/myubuntu]
0002c93bdb37: Pushed
0.1: digest: sha256:8eb87f3d6c9f2feee114ff0eff93ea9dfd20b294df0a0353bd6a4abf403336fe size: 529

 

위에서 구축한 레지스트리는 인터페이스를 따로 제공하지 않으므로 이미지를 제어하려면 도커 공식 문서의 레지스트리 항목을 참고해서 API를 사용하면 된다.

반응형