Systemd 서비스 유닛에서 환경 변수 안전하게 관리하기

Environment, EnvironmentFile, 드롭인 및 더 안전한 비밀 처리로 systemd 환경 변수를 구성합니다.

Systemd 서비스 유닛에서 환경 변수 안전하게 관리하기

환경 변수는 편리하지만 자동으로 비공개가 되지는 않습니다. systemd 서비스에서 환경 변수는 유닛 파일, 드롭인, systemctl show, 프로세스 검사 도구, 크래시 리포트, 디버그 로그 또는 복사된 지원 번들에 나타날 수 있습니다. 그렇다고 해서 환경 변수를 전혀 사용할 수 없다는 의미는 아닙니다. 환경 변수에 무엇을 넣을지, 그리고 이를 정의하는 파일을 누가 읽을 수 있는지 신중하게 결정해야 한다는 뜻입니다.

이 가이드에서는 두 가지 일반적인 지시문인 Environment=EnvironmentFile=를 다루고, 드롭인을 사용하여 로컬 구성을 패키지 관리 유닛과 분리하는 방법을 보여줍니다.


Systemd에서 환경 변수의 역할

환경 변수는 코드를 변경하지 않고 서비스를 구성할 수 있는 간단한 방법을 제공합니다. systemd가 서비스를 시작할 때 프로세스 환경을 구축하고 ExecStart=를 실행하기 전에 유닛에 정의된 변수를 적용합니다.

Systemd는 유닛 파일의 [Service] 섹션 내에서 이러한 변수를 관리하기 위한 두 가지 기본 지시문을 제공합니다.

1. 직접 정의: Environment 지시문

이 방법을 사용하면 Systemd 유닛 파일 내에서 직접 변수를 정의할 수 있습니다. 이는 거의 변경되지 않는 민감하지 않은 구성 매개변수에 적합합니다.

사용법 및 구문

Environment 지시문은 "KEY=VALUE" 형식의 변수 할당 목록을 공백으로 구분하여 허용합니다.

# /etc/systemd/system/my-app.service

[Unit]
Description=My Application Service

[Service]
User=myuser
WorkingDirectory=/opt/my-app

# 유닛 파일에 직접 변수 정의
Environment="APP_PORT=8080" "NODE_ENV=production"

ExecStart=/usr/local/bin/my-app --start

[Install]
WantedBy=multi-user.target

제한 사항 및 보안

편리하지만 Environment 지시문은 비밀번호, 토큰 또는 데이터베이스 자격 증명을 저장하기에 부적합한 곳입니다. 유닛 파일은 종종 구성 관리 시스템에 저장되거나, 티켓에 복사되거나, 서비스 동작을 검사해야 하지만 비밀을 보지 말아야 하는 운영자가 읽을 수 있습니다. 포트, 기능 플래그, 로그 수준 및 경로와 같은 값에 사용하십시오.

2. 외부 구성: EnvironmentFile 지시문

더 큰 구성의 경우 외부 파일에서 변수를 로드하는 것이 일반적으로 더 깔끔합니다. 이를 통해 기본 유닛 파일과 독립적으로 변수 파일의 권한을 관리할 수 있습니다. 또한 패키지에서 제공하는 유닛은 읽을 수 있게 유지하면서 로컬 설정은 /etc에 저장할 수 있습니다.

사용법 및 구문

EnvironmentFile 지시문은 구성 파일의 절대 경로를 사용합니다. Systemd는 이 파일을 한 줄씩 읽고 각 줄을 잠재적인 KEY=VALUE 할당으로 처리합니다.

[Service]
# 외부 파일에서 변수 로드
EnvironmentFile=/etc/config/my-app-settings.conf

ExecStart=/usr/local/bin/my-app --start

환경 파일 형식

외부 파일은 간단한 셸과 유사한 형식을 따라야 합니다.

  • #로 시작하는 줄은 주석으로 처리됩니다.
  • 빈 변수 할당(VAR=)으로 시작하는 줄은 이전에 설정된 변수가 있으면 지웁니다.
  • 변수는 KEY=VALUE로 정의됩니다.
  • 값 따옴표(KEY="VALUE WITH SPACES")가 지원됩니다.
# /etc/config/my-app-settings.conf

# 민감하지 않은 변수
MAX_WORKERS=4
LOG_LEVEL=INFO

# 민감한 변수 (엄격한 파일 권한 및 세심한 접근 제어 필요)
DB_PASSWORD=SecureRandomString12345

systemd의 환경 파일 파서가 예상한 대로 지원하지 않는 셸 습관은 피하십시오. export KEY=value를 작성하지 마십시오. 등호 주위에 공백을 두지 마십시오. 값에 공백이 포함된 경우 따옴표로 묶으십시오. 값에 리터럴 따옴표, 백슬래시 또는 개행 문자가 포함된 경우 프로덕션에서 사용하기 전에 테스트하십시오.

누락된 파일 처리

기본적으로 EnvironmentFile로 지정된 파일이 없으면 Systemd는 서비스 시작을 실패 처리합니다. 환경 파일이 선택 사항인 경우 파일 경로 앞에 하이픈(-)을 붙일 수 있습니다.

EnvironmentFile=-/etc/config/optional-settings.conf

파일 앞에 -가 붙으면 Systemd는 파일이 없어서 발생하는 오류를 무시합니다.

모범 사례: 민감한 데이터에 드롭인 유닛 사용

특히 파일이 패키지 관리자에 의해 관리되는 경우 핵심 유닛 파일(예: /usr/lib/systemd/system/my-app.service)을 수정하는 것은 일반적으로 권장되지 않습니다. 대신 드롭인 유닛 파일을 사용하여 구성 재정의 또는 추가를 적용하십시오.

이 방법은 공급업체 기본값과 로컬 구성을 분리하기 때문에 중요합니다. 또한 감사를 더 쉽게 만듭니다. 유닛은 구성이 로드되는 위치를 알려주고 해당 파일의 권한은 누가 읽을 수 있는지 알려줍니다.

단계별 드롭인 구성

1. 드롭인 디렉토리 찾기/생성

my-app.service라는 서비스의 경우 드롭인 디렉토리 이름은 my-app.service.d/여야 하며 /etc/systemd/system/ 계층 구조에 있어야 합니다.

sudo mkdir -p /etc/systemd/system/my-app.service.d/

2. 구성 재정의 생성

드롭인 디렉토리 내에 파일(예: secrets.conf)을 만듭니다. 이 파일에는 재정의하거나 추가하려는 [Service] 섹션과 특정 지시문만 있으면 됩니다.

# /etc/systemd/system/my-app.service.d/secrets.conf

[Service]
# 보안 자격 증명 파일 로드
EnvironmentFile=/etc/secrets/my-app-credentials.env

3. 외부 환경 파일 보호

이것은 가장 중요한 보안 단계입니다. 비밀이 포함된 외부 파일이 제한적인 권한을 가지고 있는지 확인하십시오. 이상적으로는 root:root가 소유하고 루트 사용자 또는 서비스 사용자만 읽을 수 있어야 합니다.

# 비밀 파일 생성
sudo touch /etc/secrets/my-app-credentials.env

# 파일에 비밀 채우기
sudo sh -c 'echo "DB_PASS=S3cr3tP@ssw0rd" >> /etc/secrets/my-app-credentials.env'

# 제한적인 권한 설정
sudo chmod 600 /etc/secrets/my-app-credentials.env

EnvironmentFile에서 참조하는 파일에 자격 증명이 포함된 경우 서비스를 관리해야 하는 계정만 읽을 수 있도록 유지하십시오. systemd가 User=로 권한을 낮추기 전에 파일을 읽을 때 0600 root:root가 일반적이지만 일부 운영 모델에서는 전용 루트 소유 그룹과 0640을 사용합니다. 중요한 것은 일반 사용자가 파일을 읽을 수 없다는 것입니다.

또한 남아 있는 위험에 대해 솔직해지십시오. 환경 변수는 하드코딩된 명령줄 인수보다 처리하기 쉽지만 여전히 완전한 비밀 관리 시스템은 아닙니다. 위험이 더 높은 자격 증명의 경우 전용 비밀 저장소, 단기 자격 증명, 최신 배포판의 systemd 자격 증명 또는 보호된 파일을 직접 읽는 애플리케이션별 메커니즘을 고려하십시오.

문제 해결 및 확인

유닛 파일 또는 드롭인을 변경한 후에는 Systemd 관리자 구성을 다시 로드해야 합니다.

sudo systemctl daemon-reload
sudo systemctl restart my-app.service

실행 중인 서비스에 대해 Systemd가 성공적으로 로드한 환경 변수를 확인하려면 systemctl show 명령을 사용하고 특히 Environment 속성을 쿼리하십시오.

systemctl show my-app.service --property=Environment

예제 출력 (로드된 변수 표시):

Environment=APP_PORT=8080 NODE_ENV=production DB_PASS=S3cr3tP@ssw0rd

이 명령은 디버깅에 유용하지만, 올바른 검사 명령을 root로 실행할 수 있는 사람은 누구나 값을 볼 수 있다는 것을 상기시켜 줍니다. 이 출력을 공유 채팅, 티켓 또는 공개 버그 보고서에 수정하지 않고 붙여넣지 마십시오.

서비스 시작에 실패하면 journalctl -xeu my-app.service를 사용하여 서비스 로그를 확인하십시오. 환경 변수와 관련된 일반적인 실패 이유는 다음과 같습니다.

  1. EnvironmentFile의 잘못된 파일 경로.
  2. 파일 누락(경로 앞에 -가 붙지 않은 경우).
  3. 외부 환경 파일의 잘못된 변수 구문(예: = 기호 주위의 공백).

작동하는 실용적인 패턴

시나리오 사용할 지시문 위치 모범 사례 보안 고려 사항
정적, 민감하지 않은 구성 Environment 직접 유닛 파일 또는 드롭인 낮은 보안 위험.
민감한 자격 증명 (비밀) EnvironmentFile 드롭인(*.service.d/)을 통해 참조되는 외부 파일 중요: 환경 파일은 0600 권한이 있어야 합니다.
모듈성 및 재정의 EnvironmentFile 드롭인 유닛 파일 구성을 공급업체 기본값과 분리합니다.

전용 드롭인 유닛 내에서 EnvironmentFile 지시문을 활용하고 엄격한 파일 권한을 보장함으로써 관리자는 최소 권한 및 관심사 분리 원칙을 준수하여 안전하고 유연하게 서비스 구성을 관리할 수 있습니다.

소규모 내부 서비스의 경우 합리적인 설정은 종종 다음과 같습니다.

# /etc/systemd/system/my-app.service.d/env.conf
[Service]
Environment="APP_ENV=production"
EnvironmentFile=/etc/my-app/runtime.env
EnvironmentFile=-/etc/my-app/local.env

runtime.env에는 필수 값이 포함됩니다. local.env는 선택 사항이며 운영자가 기본 유닛을 편집하지 않고 유지 관리 기간 동안 설정을 재정의할 수 있도록 합니다. 변경 후:

sudo systemctl daemon-reload
sudo systemctl restart my-app.service
sudo journalctl -u my-app.service -n 50 --no-pager

가장 안전한 습관은 간단합니다. 민감하지 않은 기본값은 유닛이나 일반 구성 파일에 유지하고, 비밀은 패키지 소유 유닛 밖에 두고, 자격 증명이 포함된 파일을 잠그고, 로드된 환경을 확인하되 그것이 속하지 않는 곳으로 유출되지 않도록 하십시오.

피해야 할 일반적인 실수

첫 번째 실수는 ExecStart=에 비밀을 넣는 것입니다.

ExecStart=/usr/local/bin/my-app --db-password=s3cret

서두를 때는 무해해 보이지만 명령줄 인수는 환경 파일보다 노출되기 쉬운 경우가 많습니다. 프로세스 목록, 모니터링 도구, 셸 기록, 크래시 리포트 또는 복사된 서비스 정의에 나타날 수 있습니다. 애플리케이션이 보호된 구성 파일 읽기를 지원한다면 일반적으로 더 좋습니다. 환경 변수를 예상하는 경우 보호된 EnvironmentFile=를 사용하고 명령줄에서 값을 멀리하십시오.

두 번째 실수는 공급업체 유닛을 직접 편집하는 것입니다. 패키지 업그레이드는 파일을 대체할 수 있으며 다음 재시작 시 환경 설정이 자동으로 손실될 수 있습니다. 드롭인을 사용하십시오.

sudo systemctl edit my-app.service

그런 다음 로컬 재정의만 추가하십시오.

[Service]
EnvironmentFile=/etc/my-app/my-app.env

세 번째 실수는 서비스가 터미널에서 보는 것과 동일한 셸 환경을 볼 것이라고 가정하는 것입니다. 일반적으로 그렇지 않습니다. 대화형 셸에는 .bashrc, .profile, SSH 세션 또는 배포 도구의 변수가 있을 수 있습니다. 시스템 서비스는 systemd의 관리 환경에서 시작됩니다. 앱에 PATH, JAVA_HOME, NODE_ENV, LD_LIBRARY_PATH 또는 유사한 값이 필요한 경우 명시적으로 정의하거나 절대 경로를 사용하십시오.

예를 들어, 다음은 취약합니다.

ExecStart=npm start

다음은 추론하기 더 쉽습니다.

WorkingDirectory=/opt/my-app
Environment="NODE_ENV=production"
ExecStart=/usr/bin/npm start

네 번째 실수는 필요하지 않은데 서비스 사용자가 환경 파일을 쓸 수 있도록 하는 것입니다. 자신의 환경 파일을 덮어쓸 수 있는 웹 앱은 일반적인 애플리케이션 버그를 지속성 문제로 바꿀 수 있습니다. 많은 설정에서 서비스 사용자는 애플리케이션 데이터를 읽고 로그 또는 업로드를 작성해야 하지만 서비스를 시작하는 데 사용된 자격 증명을 다시 작성할 수 없어야 합니다.

환경 변수가 잘못된 도구인 경우

환경 변수는 간단하기 때문에 인기가 있지만 항상 최상의 인터페이스는 아닙니다. 값이 크거나, 구조화되어 있거나, 자주 교체되거나, 여러 서비스에서 공유하는 경우 실제 구성 파일이나 비밀 저장소가 일반적으로 관리하기 더 쉽습니다.

데이터베이스 URL은 합리적인 환경 변수입니다.

DATABASE_URL=postgresql://[email protected]:5432/app

전체 JSON 서비스 계정 문서는 덜 즐겁습니다. 따옴표가 어색해지고, 우발적인 줄 바꿈으로 인해 오류가 발생하며, 사람들이 디버깅하는 동안 로그에 붙여넣을 가능성이 더 높습니다. 이 경우 JSON을 보호된 파일에 저장하고 파일 경로를 전달하십시오.

GOOGLE_APPLICATION_CREDENTIALS=/etc/my-app/google-service-account.json

그런 다음 JSON 파일을 별도로 보호하십시오.

sudo chown root:my-app /etc/my-app/google-service-account.json
sudo chmod 640 /etc/my-app/google-service-account.json

이것이 비밀을 마법처럼 만드는 것은 아닙니다. 애플리케이션은 여전히 읽을 수 있습니다. Root도 여전히 읽을 수 있습니다. 그러나 복잡한 비밀을 systemd의 환경 파서에 억지로 넣지 않고 파일 수준 감사를 더 명확하게 합니다.

더 안전한 검토 체크리스트

환경 변수를 사용하는 서비스를 다시 시작하기 전에 네 가지를 확인하십시오.

systemctl cat my-app.service
sudo ls -l /etc/my-app/my-app.env
sudo systemd-analyze verify /etc/systemd/system/my-app.service
sudo systemctl daemon-reload

systemctl cat은 어떤 드롭인이 활성화되어 있는지 확인합니다. ls -l은 권한이 의도한 대로인지 확인합니다. systemd-analyze verify는 다시 시작하기 전에 일부 유닛 구문 문제를 잡을 수 있습니다. 모든 애플리케이션별 설정을 검증하지는 않지만 여전히 유용한 보호 장치입니다.

다시 시작한 후 저널에서 시작 오류를 확인하십시오.

sudo systemctl restart my-app.service
sudo journalctl -u my-app.service -n 100 --no-pager

변수가 로드되었는지 확인해야 하는 경우 신중하게 쿼리하고 공유하기 전에 출력을 수정하십시오. 민감한 서비스의 경우 먼저 APP_ENV 또는 LOG_LEVEL과 같은 비밀이 아닌 변수를 확인하는 것을 선호합니다. 동일한 파일에서 로드된 경우 파일 경로와 파서 구문이 올바를 가능성이 높으며 비밀 값을 전혀 인쇄할 필요가 없을 수 있습니다.

마지막 실용적인 요점: 필요하기 전에 교체를 계획하십시오. 비밀번호나 토큰이 환경 파일에 저장된 경우 값이 변경된 후 어떤 서비스를 다시 시작해야 하는지, 그리고 해당 재시작이 다운타임을 유발하는지 기록해 두십시오. 설정하기는 쉽지만 교체하기 어려운 자격 증명은 결국 사고로 이어질 것입니다. 소규모 서비스의 경우 교체 런북은 네 줄만 있으면 됩니다.

sudoedit /etc/my-app/my-app.env
sudo systemctl restart my-app.service
sudo systemctl status my-app.service
sudo journalctl -u my-app.service -n 50 --no-pager

모든 사람이 영향 범위를 알고 있다면 그것으로 충분합니다. 대규모 시스템의 경우 교체 중에 중복될 수 있는 자격 증명을 선호하여 새 값을 배포하고, 확인하고, 서두르는 중단 시간 없이 이전 값을 제거할 수 있습니다.