Envoy 내부 구조 · 사고가 난 뒤에 읽을 글 · 이론
형식은 사고가 나기 전에만 고칠 수 있다
한 줄 요약
접근 로그는 켜고 끄는 스위치가 아니라 형식을 설계하는 일이다. 무엇을 남길지(명령 연산자), 어떤 모양으로 남길지(텍스트 · JSON), 어디에 남길지(싱크), 무엇만 남길지(필터) 네 가지를 정하는 것이고, 정해 두지 않으면 사고가 난 뒤에 남아 있는 것이 200 GET / 뿐이다.
왜 이게 필요했나
장애가 났을 때 가장 먼저 묻는 것은 늘 같다. 누가 무엇을 요청했고, 어디로 갔고, 얼마나 걸렸고, 왜 실패했는가. 이 넷을 로그 한 줄에서 답할 수 있으면 원인을 몇 분 안에 좁힌다. 없으면 애플리케이션 로그를 뒤지고, 그것도 없으면 추측이 시작된다.
문제는 이 형식을 사고가 나기 전에 정해야 한다는 것이다. 사고가 난 다음에 "업스트림 주소도 남길걸" 이라고 깨달아도 그 사고의 로그에는 이미 없다. 그래서 접근 로그는 관측 설정 중에서 유일하게 되돌릴 수 없는 부분이다.
어떻게 동작하나
명령 연산자. 형식 문자열의 %...% 가 값 하나를 뜻한다. 자주 쓰는 것만 추리면 이 정도다.
| 연산자 | 무엇 |
| --- | --- |
| %RESPONSE_CODE% | 상태 코드 |
| %RESPONSE_FLAGS% | Envoy 가 붙이는 짧은 원인 표시(UT · UF · NR 등) |
| %UPSTREAM_HOST% | 실제로 받은 업스트림의 주소와 포트 |
| %DURATION% | 요청 전체에 걸린 밀리초 |
| %BYTES_SENT% | 내보낸 바이트 수 |
| %REQ(이름)% · %RESP(이름)% | 요청 · 응답 헤더. :METHOD 처럼 콜론으로 시작하면 의사 헤더 |
값이 없을 때. 업스트림에 가지 않은 응답(direct_response)에는 %UPSTREAM_HOST% 에 담을 값이 없고, 보내지 않은 헤더도 마찬가지다. Envoy 는 그 자리를 비워 두지 않고 정해진 표시로 채운다. 로그를 기계로 읽을 때 이 표시를 진짜 값으로 오해하면 집계가 조용히 틀어진다.
텍스트냐 JSON이냐. 구분자로 자른 텍스트는 사람이 읽기 좋지만, 값 안에 구분자가 들어가는 순간 칸이 밀린다. 수집기에 보내 질의할 계획이라면 처음부터 json_format 이 낫다. 열쇠 이름은 마음대로 정할 수 있고 값 자리에 같은 연산자를 쓴다.
싱크와 필터. access_log 는 목록이라 여러 개를 둘 수 있고 각자 다른 형식과 다른 필터를 가진다. 필터는 상태 코드 · 소요 시간 · 응답 플래그 · 헤더 조건으로 걸 수 있다. 실무에서 가장 흔한 구성은 전체 로그는 짧게, 오류 로그는 자세하게 따로 두는 것이다. 한 파일에 섞으면 보관 기간을 따로 정할 수 없기 때문이다. 여기서 자주 걸리는 문법 함정 하나 — filter 는 typed_config 안이 아니라 밖, name 과 같은 층에 적는다.
현장에서 만나는 모습
"로그가 안 남습니다." 파일 싱크는 요청마다 디스크에 쓰지 않는다. 버퍼에 모았다가 주기적으로 비우고, 그 주기의 기본값은 10초다(--file-flush-interval-msec). 요청 직후에 cat 하면 비어 있는 것이 정상이다. 컨테이너가 곧바로 죽는 경우에는 정말로 버퍼째 사라지므로, 짧게 사는 사이드카에서는 이 값을 줄이거나 표준 출력으로 보낸다.
로그가 디스크를 채우는 경우. 접근 로그는 트래픽에 비례해 늘어나는 유일한 관측 신호다. 초당 수천 요청이면 하루에 수십 기가가 된다. 그래서 형식을 짧게 유지하고, 자세한 형식은 필터를 건 싱크에만 쓴다.
로그를 표준 출력으로 보내는 경우. 컨테이너 환경에서는 파일 대신 /dev/stdout 을 경로로 주는 구성이 흔하다. 수집기가 컨테이너 로그를 이미 긁어 가고 있으니 그 길에 얹는 것이다. 다만 이때는 애플리케이션 로그와 접근 로그가 한 줄기로 섞이므로, 두 가지를 구분할 표시를 형식에 넣어 두어야 나중에 갈라 볼 수 있다. 그리고 표준 출력은 파일보다 먼저 막힌다 — 받는 쪽이 느려지면 그 압력이 프록시까지 올라온다.
요청 아이디를 믿었다가 생긴 일. 요청 아이디는 여러 서비스를 지나는 한 요청을 이어 붙이는 실이고, 맨 앞의 프록시는 값이 없으면 만들어 주고 있으면 그대로 물려준다. 그 말은 밖에서 아무 값이나 넣어 보낼 수 있다는 뜻이기도 하다. 신뢰 경계 밖에서 들어오는 자리에서는 이 헤더를 지우고 새로 만드는 편이 안전하다.
공식 문서: [Access logging](https://www.envoyproxy.io/docs/envoy/v1.38.3/configuration/observability/access_log/usage) · [Command line options](https://www.envoyproxy.io/docs/envoy/v1.38.3/operations/cli)
다음 실습에서 할 것
파일 싱크를 켜고 로그가 곧바로 보이지 않는 이유를 확인한 뒤, 여덟 칸짜리 형식을 설계해 세 종류의 요청을 남긴다. 값이 없는 자리에 무엇이 찍히는지 직접 보고, JSON 형식으로 바꿔 jq 로 읽고, 500 이상만 남기는 싱크를 따로 두고, 마지막에는 요청 아이디를 클라이언트가 줄 때와 주지 않을 때가 어떻게 다른지 확인한다.