장비가 예상과 다른 주소로 통신하고 있었다. 코드를 봤다.
#define DEFAULT_HOST "192.0.2.10"
이 주소가 아니었다. 실제로는 다른 곳으로 나가고 있었다.
Table of contents
Open Table of contents
값의 출처가 넷이었다
추적해 보니 값을 정하는 자리가 넷이었다.
const char *host = DEFAULT_HOST; /* 1. 소스 기본값 */
read_config("/etc/dev.conf", &cfg); /* 2. 설정 파일이 덮음 */
if (getenv("DEV_HOST")) host = getenv(...); /* 3. 환경변수가 덮음 */
parse_args(argc, argv); /* 4. 실행 인자가 덮음 */
뒤에 오는 것이 앞을 덮는데 실제로 쓰인 것은 argv 로 들어온 넷째였고 기동 스크립트가 넘긴 값이었다.
소스를 아무리 읽어도 안 나오는데 값이 systemd 유닛 쪽 스크립트에 있기 때문이다.
이 구조 자체는 흔하고 편리하다. DEFAULT_HOST 를 보고 동작을 단정하면 반드시 틀린다.
전체 흐름 — 확인 순서
값 하나를 확인하려면 네 곳을 봐야 하고 순서가 정해져 있으니 뒤에서부터 본다.
1) 기동 스크립트 / systemd unit ← 실행 인자
2) 환경변수 ← 프로세스 환경
3) 설정 파일 ← /etc/…
4) 소스 기본값 ← 여기까지 왔으면 앞의 셋이 다 없는 것
뒤에서부터 보면 대개 첫 곳에서 끝나는데 앞에서부터 보면 소스에서 값을 찾은 순간 멈추게 된다.
찾았다고 생각한 값이 실제 값이 아니다. 순서를 거꾸로 잡는 것만으로 헛수고가 없어졌다.
검증 — 실행 중인 프로세스에서 읽기
더 확실한 방법은 프로세스가 실제로 무엇을 들고 있는지를 /proc 에서 보는 것이었다.
tr '\0' '\n' < /proc/<pid>/cmdline # 실행 인자
tr '\0' '\n' < /proc/<pid>/environ # 환경변수
/proc 에서 읽으면 설정을 재구성해 추론하는 것이 아니라 지금 도는 프로세스에서 가져오는 것이 된다.
이것을 먼저 하는 쪽이 나았다. 설정 파일을 고쳤는데 반영이 안 되는 경우도 여기서 갈린다.
파일은 바뀌었는데 environ 이 옛 값을 들고 있으면 재기동을 안 한 것이다. 파일을 보는 것과 프로세스를 보는 것이 다른 확인이었다.
상태를 보여주는 명령을 넣었다
매번 /proc/<pid>/cmdline 을 뒤지는 것도 번거로워 프로그램에 상태 출력을 넣었다.
$ devctl status
host 203.0.113.5 (from: argv)
port 9000 (from: config file)
interval 30 (from: default)
timeout 5 (from: env)
devctl status 가 값과 함께 어디서 왔는지를 찍는데 이것이 생각보다 쓸모 있었다.
설정 파일을 고쳤는데 왜 안 바뀌느냐는 물음에 (from: argv) 를 보여 주면 끝난다. 환경변수나 인자가 덮고 있다는 것을 그 줄이 말해 준다.
구현은 간단해서 값을 읽을 때 src 에 출처를 같이 기록해 두면 된다.
struct setting { const char *val; const char *src; };
val 옆에 src 를 두는 것뿐인데 조사 시간이 크게 줄었다.
참고 자료 — 문서에 적은 우선순위
운영 문서에 이 순서를 명시했다.
설정 우선순위 (뒤가 앞을 덮음)
소스 기본값 < 설정 파일 < 환경변수 < 실행 인자
현재 적용된 값은 devctl status 로 확인한다.
이것을 안 적어 두면 각자 자기가 아는 곳만 고친다. 어떤 사람은 설정 파일을 고치고 어떤 사람은 스크립트를 고친 뒤 둘 다 고쳤는데 안 된다고 말한다.
같은 말을 하는데 가리키는 자리가 달라서 대화가 안 맞는다. 순서를 문서 한 줄로 고정하니 그 대화가 없어졌다.
반대 함정도 있었다
한 가지를 더 겪었는데 /etc/dev.conf 에 없는 항목을 고치려는 경우다.
파일을 열면 열 줄쯤 있는데 프로그램이 읽는 항목은 서른 개다. 나머지 스무 개는 기본값으로 돈다.
파일에 없다고 그 설정이 없는 것이 아니고 파일은 기본값과 다르게 할 것만 담는다.
그래서 전체 항목 목록을 기본값과 함께 따로 뒀다.
# 전체 설정 항목 (값은 기본값)
host = 192.0.2.10
port = 8080
interval = 60
timeout = 10
retry = 3
...
이 목록이 없으면 무엇을 바꿀 수 있는지 알 방법이 없다. 소스를 읽어야 나오는데 그러면 설정이라는 것의 뜻이 없어진다.
정리
- 값의 출처가 여럿이면 뒤에 오는 것이 앞을 덮는다
DEFAULT_HOST같은 소스 기본값을 보고 동작을 단정하면 틀린다- 확인은 실행 인자에서 소스 쪽으로 뒤에서부터 한다
- 앞에서부터 보면 소스에서 찾은 값이 실제 값인 줄 알고 멈춘다
/proc/<pid>/cmdline과environ으로 도는 프로세스에서 직접 읽는다- 파일이 바뀐 것과 프로세스가 그 값을 든 것은 다른 확인이다
- 값과 출처를 함께 찍는 상태 명령을 넣으면 조사가 그 줄에서 끝난다
- 문서에 우선순위를 적지 않으면 각자 자기가 아는 곳만 고친다
- 설정 파일에 없다고 그 설정이 없는 것은 아니다
- 전체 항목을 기본값과 함께 따로 둔다