무중단 배포란 서비스의 중단 없이 새로운 버전을 배포하는 것을 의미한다. 이용자의 서비스 이용에 지장을 주지 않는 것과 동시에 새로운 버전을 배포하는 것이 핵심인 것이다.
이를 위해선 로드 밸런서와 두 대 이상의 서버가 필요하다.
Blue/Green 배포 - 단순 배포 전략?
이전 버전 서버와 새로운 버전 서버를 두고, 공통 로드 밸런서를 통해 새로운 버전을 배포하는 전략이다.
Blue 서버: 이전 환경 서버
Green 서버: 새로운 배포 환경
Blue/Green 전략
장점
트래픽을 새로운 버전쪽으로 옮기기 때문에 호환성 문제가 발생하지 않는다.
단점
실제 운영에 필요한 리소스보다 2배의 리소스를 확보해야 한다.(서버가 두 대이고, 일질적인 트래픽이 통하는 서버는 이전 환경이기 때문)
Rolling 전략 - 순차 배포 전략?
운영중인 복수개의 인스턴스들을 하나씩 돌아가며(Rolling) 로드 밸런서로부터 트래픽을 끊고 새로운 버전 배포를 하는 전략이다. 즉, 새로운 버전 배포가 완료가 되면 다시 로드 밸런서에 연결을 하고, 그 다음 인스턴스를 로드 밸런서로부터 끊고 새로운 버전을 배포하는 것을 반복하는 것이다.
장점
많은 서버 자원을 배포하지 않아도 무중단 배포 가능
인스턴스마다 차례로 돌아가며 배포를 진행하므로, 배포로 인한 위험이 줄어든다.
단점
새 버전을 배포할 때마다 서비스 중인 인스턴스의 개수가 줄어, 서버 부담이 증가한다.
배포가 진행되는 동안 새로운 버전과 이전 버전의 호환성 문제가 발생할 수 있다.
Canary 배포 - 위험 감지 전략?
Canary 전략
이전 버전과 새로운 버전이 동시에 가동되는 방식으로, 새 버전 인스턴스는 일부 사용자에게만 서비스 하는 방식이다. 새 버전이 정상적으로 작동함을 확인하면, 전체 트래픽을 새 버전으로 전환한다. 오류가 발생한다면, 일부 사용자에게만 발생하므로 오류 방지에 효과적이다.
참고 (개인적 의견)
Canary 전략이라는 이름이 붙은 이유는 탄광에서 위험 감지용으로 데려가던 것에서 유래한 것 같다.
장점
사용자 테스트와 무중단 배포가 동시에 가능함
새로운 버전으로 인한 위험을 감소시킬 수 있다.
단점
Rolling 전략과 마찬가지로 이전과 새로운 버전이 동시에 존재하므로 호환성 문제가 발생 가능하다.
프로세스? 프로세스가 무엇인가. 여기서 시작해야 도커에 대한 설명을 할 수 있겠다. 우리가 주로 컴퓨터에 설치하는 것들을 ‘프로그램‘이라고 한다. 카카오톡, 알약, 팟플레이어 등… 모든것이 프로그램이라고 할 수 있다. 하지만 프로그램은 설치되었다고 해서 실행되는 것이 아니다. 실행을 시켜야 그 프로그램을 사용할 수 있다. 정말 러프하게 설명하자면 ‘실행되고 있는 프로그램을 프로세스‘라고 한다. 조금 전문적으로 설명하자면 CPU가 작업을 처리하기 위해 ‘메모리에 올라가 있는 프로그램‘이라고 말할 수 있겠다.
그래서 프로세스가 도커랑 뭐?
자 이제 프로세스가 도커와 무슨 상관일까? 도커를 러프하게 설명하자면, 프로세스를 구동하고 있는 가상 머신이다. 오.. 그렇구나. 그런데 가상머신 VM과 도커랑 무슨 차이가 있길래 구별을 하는거냐? 이것 역시 러프하게 얘기하자면, VM은 모든 가상 머신에 각각 OS가 설치되어있다. 내 컴퓨터에 가상 머신이 4개가 돌아가고 있다 가정한다면, 4개의 컴퓨터가 하나의 컴퓨터 안에서 돌아가고 있다고 생각해도 무방하다. 때문에 무겁다. 하지만 도커는 호스트의 OS를 공유하기 때문에 가상머신에 비해 비교적 빠르고 가볍다.
도커의 구성 요소?
자 이제 그러면 도커를 구성하는 요소들에 대해 설명하겠다. 도커는 크게 호스트, 컨테이너로 구성된다. 호스트란 가상 머신을 지칭하는 거로, OS가 구동된다. 컨테이너는 이 호스트에서 하나 이상의 프로세스를 구동하고 있는 단위이다. 우리의 컴퓨터랑 똑같이 생각하면 이해가 빠를 것이다. 컴퓨터를 켜서 배경화면에 와서(호스트 접속) 프로그램을 실행하면(컨테이너 구동) 프로그램을 사용할 수 있다. 물론 이 예시에서 클라이언트는 빠졌지만.
도커를 이용하는 방법
그렇다면 도커 컨테이너를 구동시키기 위하여 어떻게 해야 할 것인가? 우선 도커 컨테이너는 이미지라는 것을 가져와 프로세스를 구동시킨다. 방법은 두가지가 있다. 첫번째는 Dockerfile을 통해 현재 로컬 호스트의 소스코드와 구동 환경들을 정적으로 이미지 빌드하여 사용하는 것이다. 이 경우 커스텀한 이미지를 만들어 사용할 수 있다는 장점이 있다. 두번째는 레지스트리에서 가져오는 것이다. 레지스트리가 무엇인가? 쉽게 말하자면 이미지를 온라인상에 업로드 한 저장창고 라고 생각하면 쉽다. 가장 대표적으로 도커 공식 레지스트리 docker hub가 있다.
도커 이미지는 빌드되어 정적인 상태로 관리된다. 때문에 저장장치에 이미지를 업로드하여 url을 통해 내려 받아 와 사용할 수 있다. 이른바 CI/CD 의 일종으로 볼 수 있을 것 같다. 그런데 개인적인 프로젝트나, 회사 내부 프로젝트일 경우 이미지는 내부에서 관리되어야 하고 유출되면 곤란하다. 때문에 도커 이미지 레지스트리 관리 오픈소스 툴인 harbor 를 이용해 이미지를 관리해 왔다.
보안
이미지를 업로드한 저장소에 접근하기 위해선 저장소 접근 권한을 통해 로그인을 해야 한다. 그런데 리눅스 기반의 운영체제에서 (다른 운영체제는 잘 모르겠다) docker login 명령어를 통해 접근하려고 하면, 로컬에 해당 민감정보가 저장되어 버린다는 끔찍한 문제점이 존재한다. 때문에 도커에서도 공식적으로 이러한 방식이 아닌 credential을 이용해 접근하라고 권고하고 있다.
#!/bin/sh
# Sets up a docker credential helper so docker login credentials are not stored encoded in base64 plain text.
# Uses the pass secret service as the credentials store.
#
# If previously logged in w/o cred helper, docker logout <registry> under each user or remove ~/.docker/config.json.
#
# For Swarm use just run once on a manager.
# Script written for use on Ubuntu.
# Run elevated logged in as the target user.
# To remove cred helper:
# - delete /usr/local/bin/docker-credential-pass
# - remove '{ "credsStore": "pass" }' from ~/.docker/config.json
# Ensure executable in git:
# git update-index --chmod=+x path/docker-credentials.sh
if ! [ $(id -u) = 0 ]; then
echo "This script must be run as root"
exit 1
fi
# Install dependencies - jq more optional for existing varying configuration in ~/.docker/config.json.
apt update && apt-get -y install gnupg2 pass rng-tools jq
# Check for later releases at https://github.com/docker/docker-credential-helpers/releases
version="v0.6.3"
archive="docker-credential-pass-$version-amd64.tar.gz"
url="https://github.com/docker/docker-credential-helpers/releases/download/$version/$archive"
# Download cred helper, unpack, make executable, move it where it'll be found
wget $url \
&& tar -xf $archive \
&& chmod +x docker-credential-pass \
&& mv -f docker-credential-pass /usr/local/bin/
# Done with the archive
rm -f $archive
config_path=~/.docker
config_filename=$config_path/config.json
# Could assume config.json isn't there or overwrite regardless and not use jq (or sed etc.)
# echo '{ "credsStore": "pass" }' > $config_filename
if [ ! -f $config_filename ]
then
if [ ! -d $config_path ]
then
mkdir -p $config_path
fi
# Create default docker config file if it doesn't exist (never logged in etc.). Empty is fine currently.
cat > $config_filename <<EOL
{
}
EOL
echo "$config_filename created with defaults"
else
echo "$config_filename already exists"
fi
# Whether config is new or existing, read into variable for easier file redirection (cat > truncate timing)
config_json=`cat $config_filename`
if [ -z "$config_json" ]; then
# Empty file will prevent jq from working
$config_json="{}"
fi
# Update Docker config to set the credential store. Used sed before but messy / edge cases.
echo "$config_json" | jq --arg credsStore pass '. + {credsStore: $credsStore}' > $config_filename
# Output / verify contents
echo "$config_filename:"
cat $config_filename | jq
# Help with entropy to prevent gpg2 full key generation hang
# Feeds data from a random number generator to the kernel's random number entropy pool
rngd -r /dev/urandom
# To cleanup extras from multiple runs: gpg --delete-secret-key <key-id>; gpg --delete-key <key-id>
echo "Generating GPG key, accept defaults but consider key size to 2048, supply user info"
gpg2 --full-generate-key
echo "Adjusting permissions"
sudo chown -R $USER:$USER ~/.gnupg
sudo find ~/.gnupg -type d -exec chmod 700 {} \;
sudo find ~/.gnupg -type f -exec chmod 600 {} \;
# List keys
gpg2 -k
# Grab target key
key=$(gpg2 --list-secret-keys | grep uid -B 1 | head -n 1 | sed 's/^ *//g')
echo "Initializing pass with key $key"
pass init $key
# Image can't be found when Swarm attempts to pull later if a pass phrase is here.
echo "Do not set a passphrase for this step (*IMPORTANT*)"
pass insert docker-credential-helpers/docker-pass-initialized-check
# Optionally show password but mask ***
# pass show docker-credential-helpers/docker-pass-initialized-check | sed -e 's/\(.\)/\*/g'
echo "Docker credential password list (empty initially):"
docker-credential-pass list
echo "Done. Ready to test. Run: docker login <registry>"
echo "After login run: docker-credential-pass list; cat ~/.docker/config.json; pass show"