프로세스? 프로세스가 무엇인가. 여기서 시작해야 도커에 대한 설명을 할 수 있겠다. 우리가 주로 컴퓨터에 설치하는 것들을 ‘프로그램‘이라고 한다. 카카오톡, 알약, 팟플레이어 등… 모든것이 프로그램이라고 할 수 있다. 하지만 프로그램은 설치되었다고 해서 실행되는 것이 아니다. 실행을 시켜야 그 프로그램을 사용할 수 있다. 정말 러프하게 설명하자면 ‘실행되고 있는 프로그램을 프로세스‘라고 한다. 조금 전문적으로 설명하자면 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"