1. Overview

OHS 패치 후 일부 ProxyPass 경로가 backend로 전달되지 않고 404로 처리될 수 있다.

이는 Apache HTTP Server의 Commit 17770c2에서 변경된 matchable worker 처리와, 포괄 ProxyPassMatch 및 개별 ProxyPass가 같은 backend를 사용하는 구성의 상호작용으로 발생할 수 있다.



2. Descriptions

2.1 $1과 matchable worker

다음 설정에서 $1은 요청마다 달라지는 backreference다.

1
ProxyPassMatch "^/(.*\.gif)$" "http://backend:8000/$1" timeout=30

과거에는 설정 시 등록된 http://backend:8000/$1 worker와 실제 요청의 http://backend:8000/a.gif worker가 정확히 일치하지 않았다.

1
2
Configured worker : http://backend:8000/$1
Request worker    : http://backend:8000/a.gif

따라서 $1에 들어간 값에 대응하는 configured worker를 찾지 못하면, timeout=30이 설정된 worker 대신 default reverse-proxy worker가 선택될 수 있었다.

Commit 17770c2는 $1을 고정 문자열이 아닌 가변 매칭 영역으로 처리하는 matchable worker를 도입했다. 이제 /a.gif, /b.gif와 같이 $1 값이 달라도 동일 configured worker를 찾아 설정값을 적용할 수 있다.

2.2 Worker hierarchy와 설정 등록 충돌

mod_proxy는 같은 backend에 대한 중복 worker 생성을 피하기 위해 backend URL의 공통 부모를 기준으로 worker를 관리한다.

1
2
3
http://backend:8000/          <- parent worker
|- http://backend:8000/app-a  <- child worker
`- http://backend:8000/app-b  <- child worker

이 구조는 connection pool 등의 backend 자원을 불필요하게 중복 생성하지 않도록 한다.

다음처럼 포괄 ProxyPassMatch가 먼저 선언된 구성을 보자.

1
2
3
4
ProxyPassMatch "^/(?!app-a|app-b)" "http://backend:8000/"

ProxyPass /app-a http://backend:8000/app-a
ProxyPass /app-b http://backend:8000/app-b

OHS 기동 중 첫 번째 규칙은 root URL에 matchable worker를 만든다. 이후 일반 ProxyPass를 읽으면 mod_proxy는 기존 root worker를 같은 hierarchy의 worker로 찾는다.

Commit 17770c2 이후 이 기존 worker가 matchable worker이고 새 worker가 일반 worker이면 둘을 이전 방식으로 병합하지 않는다. 이 코드 경로에서는 일반 worker를 별도로 생성하는 대신, 후속 ProxyPass alias 등록을 rollback할 수 있다.

즉, OHS가 기동된 뒤 /app-a 또는 /app-b의 유효한 proxy alias가 남지 않을 수 있다. 이 경우 요청은 backend로 전달되지 않고 OHS의 로컬 경로 처리로 넘어가 404가 발생한다.

OHS BP별 구현 확인

해당 Apache commit의 OHS 반영 시점을 추적하기 위해, 이슈가 재현되지 않는 BP와 재현되는 BP 사이의 중간 패치를 내려받아 mod_proxy.so를 비교했다. 비교 대상은 모두 Linux x86-64용 OHS 12.2.1.4 DB19C BP다.

패치 번호 패치 이름 AH10249 / ap_proxy_define_match_worker
35835034 OHS (Native) DB19C Bundle Patch 12.2.1.4.230922 없음
36134205 OHS (Native) DB19C Bundle Patch 12.2.1.4.231222 없음
36393221 OHS (Native) DB19C Bundle Patch 12.2.1.4.240312 없음
36747799 OHS (Native) DB19C Bundle Patch 12.2.1.4.240619 확인됨
38856415 OHS (Native) DB19C Bundle Patch 12.2.1.4.260116 확인됨
39611663 OHS (Native) DB19C Bundle Patch 12.2.1.4.260624 확인됨

패치 ZIP에서 추출한 모듈은 다음과 같이 해시, 로그 문자열, ELF 함수 심볼을 확인할 수 있다.

1
2
3
sha256sum mod_proxy.so
strings -a mod_proxy.so | grep AH10249
readelf -Ws mod_proxy.so | grep ap_proxy_define_match_worker

230922·231222·240312의 모듈은 SHA-256이 동일했다. 반면 Patch 36747799 — OHS (Native) DB19C Bundle Patch 12.2.1.4.240619부터 AH10249 로그 코드와 ap_proxy_define_match_worker 함수 심볼이 확인됐다. 따라서 해당 commit과 관련된 구현은 240619 BP부터 반영된 것으로 보인다. 이는 배포 바이너리의 정적 비교에 근거한 판단이며, commit 전체의 동일성이나 실행 시 동작까지 증명하는 것은 아니다.

2.3 Resolution

구체적인 ProxyPass를 먼저 선언하고, 포괄 ProxyPassMatch를 마지막에 선언한다.

1
2
3
4
5
6
7
ProxyPass /app-a http://backend:8000/app-a
ProxyPassReverse /app-a http://backend:8000/app-a

ProxyPass /app-b http://backend:8000/app-b
ProxyPassReverse /app-b http://backend:8000/app-b

ProxyPassMatch "^/(?!app-a|app-b)" "http://backend:8000/"

문제는 runtime request matching이 아니라 OHS 기동 시 configuration 등록 단계에서 발생한다. 패치 후 일부 proxy 경로가 사라진 경우에는 ProxyPassMatch와 개별 ProxyPass의 선언 순서 및 backend URL hierarchy를 함께 확인한다.



3. References

Apache HTTP Server Commit 17770c2

mod_proxy ProxyPass Directive