최근에 주변 지인이 진행하는 프로젝트의 배포를 돕는 과정에서 Docker의 환경 변수를 주입하는 방법을 이것저것 설명해 주었다. 주된 내용은 빌드 타임의 환경 변수 주입과 런타임의 환경 변수 주입이 다르다는 이야기였는데, 결과적으로는 잘 해결이 되었으나 설명하면서도 살짝 애매한 부분이 있었다.
환경 변수라는 것을 넣어줄 수 있는 부분은 굉장히 많다. (1)Dockerfile에 정의해 줄 수도 있다. ENV나 ARG가 이에 해당한다. 그리고 (2). env 파일을 만들어서 dotenv를 사용해 애플리케이션에서 환경 변수를 끌어다 쓸 수 있게 해 줄 수 있다. 그리고 마지막으로는 (3) 바깥쪽의 오케스트레이션 툴(Kubernetes, docker-compose)도 환경 변수를 주입해 줄 수 있는데, 만약 셋의 경우에서 모두 환경 변수를 넣어주게 된다면 어떤 것이 적용될까? 우선순위는 어떻게 될까?
경험적인 부분으로 당연히 애플리케이션 레벨의 환경 변수보다, OS 레벨의 환경 변수를 우선적으로 사용하는 것을 알고 있기는 했다. docker-compose만 써보아도 그렇게 되는 건 안다. 하지만 막상 설명하려니 '그냥 그렇다' 수준에서 설명할 수밖에 없었고, 납득이 되는 최소한의 수준까지의 설명과 성공적으로 배포는 되었지만 개인적으로 궁금증이 남아서 조금 더 확인을 해보았다. 그냥 애플리케이션 레벨이랑 운영체제의 레벨이랑 다르다 정도로 넘어갔다.
이번 포스팅에서는 간단하게 dockerfile을 통해서 환경 변수를 사용하는 법을 다룬 후에, 위에서 상술한 환경 변수를 주입하는 다양한 케이스를 파해친다. 기술적인 궁금증을 해소하는 과정은 뒤에 있으니 깊게 궁금하지 않다면 앞 부분만 봐도 좋을 것 같다.
본론
ARG vs ENV
Dockerfile(도커 파일)을 작성하고 이미지를 통해 배포를 하는 상황이 된다면, 당연히 환경 변수에 대한 것을 고려할 수 밖에 없다. 내부적으로 테스트하는 데 쓰이는 알파 환경과 사용자용으로 쓰이는 프로덕션 환경을 구분해야 하기 때문이다. 프로그래밍을 할 때, 그리고 엔지니어로서 기본적인 마음가짐은 확장성을 고려하는 것이다. 환경 변수를 유념해서 도커파일을 작성하면, 어떤 환경에서도 쓸 수 있는 좋은 컨테이너가 만들어진다.
그렇다면 도커 파일을 어떻게 작성해야지, 손쉽게 확장할 수 있는 환경 변수를 가질까? Dockerfile에서는 환경 변수를 설정하는 것은 다음의 두 가지를 일반적으로 쓴다. ARG와 ENV가 있다.
- ARG : 이미지를 빌드할 때 사용하며, docker build --build-arg VERSION=2.0의 형태로 사용한다.
- ENV : Dockerfile 빌드 시에 이미지 내부에 기록되며, 컨테이너 실행 시에도 환경 변수로 자동 등록된다. docker run --env VERSION=2.0의 형태로 오버라이딩할 수 있다.
ARG는 이미지를 빌드할 때 사용하며, ENV는 컨테이너 실행 시에 사용되기도 한다고 하니, ENV가 ARG보다 좀 더 큰 개념이다. 이에 대해서 이해하기 쉽게 설명된 그림 자료가 있다.
즉, ARG로 설정한 것은 빌드 타임에 할당해줄 수 있으며 이후에 변경이 불가능하다. 반면에 ENV로 설정한 것은 이미지 내부에 저장되며, 나중에 컨테이너가 실행될 때 환경 변수가 된다.
여기까지의 내용을 요약하면, docker run이나 docker-compose 및 kubernetes를 통해서 확장이 가능하게 설계하기 위해서는 ENV를 써야한다는 것이다. 외부에서 DATABASE_URL이나 PORT 등을 달리하여 컨테이너를 띄울 수 있는 것이다.
여기까지가 기본적인 Dockerfile에 대한 상식이다. 하지만 우리가 복잡한 아키텍처를 구성하게 된다면 여기저기에서 환경 변수가 끼어들게 된다. 그렇다면 그 우선순위는 어떻게 되는 걸까? 누가 정해주는 걸까? 이에 대해서 조사한 결과 리눅스에 대한 기본적인 CS 지식으로 우선순위를 결정된다는 것을 알게 되었다. 포인트는 위에 쓰인 것처럼 ENV로 설정한 것은 환경 변수가 된다는 것이다.
환경 변수?
docker docs에서 dockerfile의 ENV에 대한 설명은 위와 같다. application이 환경 변수를 사용할 때, ENV로 설정하는 것인데, 밑에 재미있는 내용이 있다.
This sets a Linux environment variable we'll need later.
이것은 나중에 사용할 리눅스 환경 변수를 설정한다.
나중에 사용할 리눅스 환경 변수를 설정하는 것은 무엇일까? 이에 대한 해답은 손쉽게 찾아볼 수 있을 것이다. docker container에 exec 명령어로 붙어, 리눅스에서 사용하는 셸 명령어인 env를 확인해보면 된다.
위 사진은 간단하게 docker run redis 명령어를 통해 레디스 명령어를 띄운 후, 셸로 진입하여 env를 실행한 결과이다. 컨테이너의 환경 변수에 대한 부분을 찾아볼 수 있다. 앞에 상술되어있는 리눅스 환경 변수의 의미는 이것이다. 컨테이너에서 전체적인 환경 변수로써 사용하는 환경 변수를 ENV로 나타낸다.
# ...
ENV REDIS_VERSION 7.4.2
ENV REDIS_DOWNLOAD_URL http://download.redis.io/releases/redis-7.4.2.tar.gz
ENV REDIS_DOWNLOAD_SHA 4ddebbf09061cbb589011786febdb34f29767dd7f89dbe712d2b68e808af6a1f
# ...
실제로 redis 7.4.2 버전의 dockerfile을 보면 위와 같이 ENV로 정의되어있는 것을 알 수 있다. 위처럼 정의된 ENV값이 그대로 레디스 컨테이너에 들어가게 된다. 여기까지 기본적인 dockerfile이 환경 변수를 처리하는 방법에 대한 것이다. ARG보다는 ENV를 쓰는 것이 거의 엔간한 상황에서 좋으며, ENV로 설정한 내용은 컨테이너를 실행시킨 후에 리눅스의 env 명령어로 확인할 수 있다.
맨 처음에 상술한 세 가지의 환경 변수 주입하는 방법 중에 하나는 끝났다. 나머지 두 가지의 궁금증 해소가 필요하다.
- dotenv와 .env 파일은 뭘까? 리눅스의 환경 변수와는 다른 건가?
- 컨테이너를 실행시킬 때, 외부(K8s, docker-compose)에서 주입한 환경 변수는 어디로 가는 건가?
남은 두 가지의 테스트를 위해서 실험 환경을 구성하고 진행해 보았다.
실험 환경
테스트를 위해 다음과 같은 순서로 스크립트를 만들어준다. 하나의 라이브러리가 필요하다. 편의상 파이썬이 깔려있다고 생각하고, 다음의 패키지를 깔자. 나중에 uninstall로 지울 수 있다.
pip install python-dotenv
그리고 test.py를 다음과 같이 작성했다.
import os
from dotenv import load_dotenv
load_dotenv()
print(os.environ.get("DOMAIN"))
print(os.environ.get("ADMIN_EMAIL"))
print(os.environ.get("ROOT_URL"))
그리고 .env는 다음과 같이 작성했다.
DOMAIN=example.org
ADMIN_EMAIL=admin@${DOMAIN}
ROOT_URL=${DOMAIN}/app
그 후에 파이썬 스크립트를 실행하면 다음과 같은 결과를 볼 수 있다.
➜ test python3 test.py
example.org
admin@example.org
example.org/app
.env로 설정된 환경 변수를 잘 받아온 모양이다. 우리가 아는 애플리케이션을 일반적으로 이와 같이 환경 변수를 받아서 쓰도록 되어있다. 개발을 처음 시작할 때부터 관습적으로 그냥 그래왔다.
dotenv란?
그냥 생각 없이 .env 파일을 만들고, typescript를 쓰던, python을 쓰던 적당한 dotenv 라이브러리를 통해서 끌어다 써왔지만, 문득 생긴 궁금증이다. OS 레벨의 환경 변수와는 어떤 차이가 있을까? 먼저 위 코드베이스는 python-dotenv 라이브러리 공식 문서에서 가져왔다.
- [pypi] python-dotenv : https://pypi.org/project/python-dotenv/
python-dotenv
Read key-value pairs from a .env file and set them as environment variables
pypi.org
맨 윗줄을 살펴보면, 다음과 같은 핵심 문장이 나와있다.
Python-dotenv reads key-value pairs from a .env file and can set them as environment variables. It helps in the development of applications following the 12-factor principles.
Python-dotenv는 .env 파일로부터 키-값 쌍을 읽어서 환경 변수로 설정할 수 있습니다. 이는 12-factor 원칙을 따르는 애플리케이션 개발에 도움을 줍니다.
먼저, .env 파일의 key-value를 환경 변수로 설정할 수 있다고 한다. 즉, 정확히 말하면 내가 다른 사람에게 설명할 때 쓰인 애플리케이션 레벨의 환경 변수라는 것은 없는 셈이다. 관념적인 키워드고 정확한 원리는 운영체제에서 환경 변수를 끌어다 쓰고자 하는데, 개발 환경에서는 그럴 수 없으니까 .env라고 하는 파일을 통해서 대체할 수 있도록 돕는다는 것이다. 공식 깃허브에 쓰여있는 README.md 내용은 다음과 같다.
애플리케이션이 12-factor 애플리케이션처럼 환경 변수로부터 설정 정보를 가져온다면, 직접 환경 변수들을 설정해야 하기 때문에 개발 환경에서 실행하는 것이 그다지 실용적이지 않을 수 있습니다.
이를 돕기 위해, 애플리케이션에 Python-dotenv를 추가하여, 예를 들어 개발 환경에서 .env 파일이 존재할 경우 해당 파일로부터 설정 정보를 불러오도록 할 수 있습니다. 이렇게 하면 여전히 환경 변수를 통해 설정을 조정할 수 있습니다.
12-factor라는 내용이 나오는데, 뭔가 고풍스러운 분위기를 풍기는 사이트와 개발할 때 지켜야 하는 철학에 대해서 나온다. 시간이 되면 한 번 읽어보면 재미있을 것 같다. 여기서 세 번째 항목에서 환경 변수를 다루는 방법에 대해서 나온다.
- [12-factor] config : https://12factor.net/ko/config
The Twelve-Factor App (한국어)
III. 설정 환경(environment)에 저장된 설정 애플리케이션의 설정은 배포 (스테이징, 프로덕션, 개발 환경 등) 마다 달라질 수 있는 모든 것들입니다. 설정에는 다음이 포함됩니다. 데이터베이스, memca
12factor.net
결국 dotenv로 설정하는 것은 운영체제의 환경변수와 다른 수준의 환경 변수가 아니라 운영체제의 환경 변수 대신에 사용한다는 것이다. 한 가지 내가 찾고자 하는 내용이 빠져있는데, dotenv와 docker container에 설정되어 있는 환경 변수 중 어떤 것이 오버라이딩하는 것일까? 실험 환경을 통해 확인해 보자.
환경 변수 우선순위 확인
위 실험 환경의 python3의 dotenv를 실험하는 환경에 아래와 같이 dockerfile을 써보자.
FROM python:3.10-slim
WORKDIR /app
RUN pip install --no-cache-dir python-dotenv
COPY . .
# 환경 변수 주입
ENV DOMAIN=thisisdockerfile
CMD ["python", "test.py"]
그 후에 dockerfile을 빌드하고 실행하면 다음과 같은 결과를 얻을 수 있다.
(venv) ➜ test docker run realtest
thisisdockerfile
admin@thisisdockerfile
thisisdockerfile/app
그렇다면 마지막으로, docker run 명령어에 파라미터로 --env를 통해서 환경 변수를 넣어주면 어떻게 될까?
(venv) ➜ test docker run --env DOMAIN=thisisdockerruncli realtest
thisisdockerruncli
admin@thisisdockerruncli
thisisdockerruncli/app
다행히 예상했던 대로 나왔다. 환경 변수의 우선순위는 일반적으로 다음과 같다. ( Dockerfile에 ENV로 환경 변수를 적용했을 경우 )
- Docker run, docker-compose, Kubernetes 등 외부에서 주입하는 환경 변수
- Dockerfile에 쓰여있는 ENV 환경 변수
- .env 파일과 dotenv 라이브러리를 통해 설정한 환경 변수
위와 같은 순서대로 우선순위를 가지고 있다는 것을 알 수 있다. 위는 일반적인 경우에 해당하며, 좀 더 찾아보니 다음과 같은 dotenv의 파라미터가 있었다.
여기서, override라는 부분이 있는데, 원하는 내용이 들어있었다. 기본값은 False이기 때문에 시스템의 환경 변수가 우선시 된다. 이를 True로 주게 되면 .env 파일에 있는 환경 변수를 최우선으로 사용하도록 설정해 줄 수 있다.
결론
Dockerfile이나 그 외에 도커 이미지 기반의 툴을 사용해 보았으면 경험적으로 알겠지만, 한 문장으로 정리하면 가장 바깥쪽에서 넣어주는 환경 변수가 적용된다는 점이다. 애플리케이션의 .env 파일의 바깥에 Dockerfile 그 바깥에 docker-compose가 있듯이 똑같은 우선순위로 환경 변수가 적용된다.
마치며
경험적으로 왠지 그렇지 않을까..? 하고 있었던 부분이지만 직접 이렇게 실험을 통해서 확인해 보면서 환경 변수에 담긴 심오한 철학을 또 확인해 볼 수 있었다..
'DevOps > docker' 카테고리의 다른 글
docker은(는) 사용자의 컴퓨터를 손상시킵니다. "'com.docker.vmnetd'에 악성 코드가 포함되어 있어서 열리지 않았습니다." 문제 해결 방법 (2) | 2025.01.14 |
---|---|
꼭 알아야 하는 CMD와 ENTRYPOINT의 차이점 (0) | 2024.12.09 |
docker multi-stage build에 대해서 ( spring ) (0) | 2024.11.25 |
[Jenkins] DinD vs DooD 그리고 DooD 설정법 (1) | 2024.10.30 |
docker의 경량화 버전인 enroot (0) | 2024.07.13 |