- Published on
Azure VM IMDS 169.254.169.254 접근 실패 원인·해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버에서 Managed Identity 토큰을 받기 위해 IMDS(Instance Metadata Service)로 호출했는데 169.254.169.254가 타임아웃/연결 거부/403/404로 실패하는 경우가 생각보다 자주 발생합니다. 특히 기업망 프록시, 보안 에이전트, 커스텀 라우팅(UDR), 컨테이너 네트워킹(CNI) 같은 요소가 하나만 끼어도 “로컬 링크(local link) IP” 특성 때문에 원인 파악이 어렵습니다.
이 글은 Azure VM에서 IMDS 접근 실패를 재현 가능한 체크리스트로 분해해, “어디서 막히는지”를 빠르게 찾고 “어떻게 고치는지”까지 연결합니다.
> 디버깅 접근법은 결국 체크리스트 싸움입니다. 비슷한 맥락으로 장애를 체크리스트로 푸는 글로는 Assistants API v2 run이 queued나 in_progress에 멈출 때 실전 디버깅 체크리스트도 참고하면 흐름이 도움이 됩니다.
IMDS(169.254.169.254) 기본 동작 이해
169.254.169.254는 링크-로컬 주소로, 인터넷/사설망 라우팅이 아니라 호스트(플랫폼)로 바로 연결되는 특수 경로입니다.- Azure VM 내부에서만 접근 가능하며, 일반적으로 다음 두 API를 사용합니다.
- 메타데이터 조회:
http://169.254.169.254/metadata/instance?... - Managed Identity 토큰:
http://169.254.169.254/metadata/identity/oauth2/token?...
- 메타데이터 조회:
IMDS 호출은 보통 다음 조건을 요구합니다.
- HTTP 헤더
Metadata: true필수 api-version쿼리 필수- 토큰 요청 시
resource또는scope파라미터 필요(라이브러리에 따라 다름)
증상별로 보는 빠른 분류(가장 중요)
IMDS 장애는 에러 메시지/증상으로 먼저 분류하면 시간을 크게 줄일 수 있습니다.
1) Connection timed out / Operation timed out
대부분 네트워크 경로가 IMDS로 가지 못함입니다.
- 프록시가 링크-로컬 트래픽을 가로챔
- iptables/방화벽이 169.254.169.254 차단
- 커스텀 라우팅/정책 라우팅으로 로컬 링크가 꼬임
- 컨테이너 네트워크에서 호스트로의 경로가 없음
2) Connection refused
- 로컬에서 해당 IP:80으로 연결을 시도했지만 즉시 거절
- 흔치 않지만 보안 에이전트/로컬 프록시가 80 포트를 인터셉트하는 케이스에서 보입니다.
3) HTTP 400/404
- URL/파라미터 문제(특히
api-version, 경로 오타) Metadata: true누락
4) HTTP 401/403
- Managed Identity 자체가 미할당(시스템/사용자 ID)
- 토큰 요청 파라미터(resource/scope) 오류
- (드물게) 정책/보안 설정에 의해 IMDS 기능 제한
가장 먼저 해볼 “정상 호출” 기준 만들기
문제 해결의 핵심은 “이 VM에서 IMDS가 원래 되는지”를 curl 한 방으로 확인하는 것입니다.
Linux에서 메타데이터 호출
curl -sS -D - \
-H "Metadata: true" \
"http://169.254.169.254/metadata/instance?api-version=2021-02-01" \
| head
정상이라면 HTTP/1.1 200 OK와 함께 JSON이 내려옵니다.
Linux에서 Managed Identity 토큰 호출
RESOURCE="https://management.azure.com/"
curl -sS -D - \
-H "Metadata: true" \
"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=${RESOURCE}" \
| head -c 400
access_token이 내려오면 IMDS 경로는 정상입니다.- 400/401/403이면 “네트워크”가 아니라 “ID/요청 파라미터” 문제일 확률이 큽니다.
Windows PowerShell에서 호출
$headers = @{ Metadata = "true" }
Invoke-RestMethod -Headers $headers -Method GET `
-Uri "http://169.254.169.254/metadata/instance?api-version=2021-02-01"
원인 1) 프록시(HTTP_PROXY/HTTPS_PROXY)가 IMDS 트래픽을 가로챔
기업 환경에서 가장 흔한 원인입니다. 애플리케이션/SDK가 HTTP_PROXY를 따라가면, 로컬 링크 주소인 169.254.169.254까지 프록시로 보내버려 타임아웃이 납니다.
진단
env | egrep -i 'http_proxy|https_proxy|no_proxy'
HTTP_PROXY/HTTPS_PROXY가 설정되어 있고NO_PROXY에169.254.169.254가 없으면 위험 신호입니다.
해결: NO_PROXY에 IMDS 예외 추가
export NO_PROXY="169.254.169.254,localhost,127.0.0.1,${NO_PROXY}"
export no_proxy="$NO_PROXY"
systemd 서비스라면 유닛 파일에 반영합니다.
# /etc/systemd/system/myapp.service.d/proxy.conf
[Service]
Environment="NO_PROXY=169.254.169.254,localhost,127.0.0.1"
Environment="no_proxy=169.254.169.254,localhost,127.0.0.1"
적용 후:
sudo systemctl daemon-reload
sudo systemctl restart myapp
> 프록시/환경변수 문제는 EKS/CI에서도 자주 장애를 만듭니다. 체크리스트 방식의 접근이 익숙하다면 EKS TLS handshake timeout 원인·해결 9가지처럼 “네트워크 경로-프록시-인증서-리졸브” 순으로 쪼개보는 방식이 그대로 적용됩니다.
원인 2) iptables / 방화벽 / 보안 에이전트가 링크-로컬을 차단
서버 하드닝 과정에서 “특수 대역 차단” 규칙이 들어가면 IMDS가 같이 막히기도 합니다.
Linux 진단
sudo iptables -S | grep -E "169\.254\.169\.254|169\.254\.0\.0"
sudo nft list ruleset | grep -E "169\.254\.169\.254|169\.254\.0\.0" -n
또는 실제 패킷이 나가는지 확인:
sudo tcpdump -ni any host 169.254.169.254 and port 80
- curl을 날렸는데 tcpdump에 SYN 자체가 안 보이면 로컬 스택에서 드롭될 가능성이 큽니다.
해결
- 169.254.169.254:80에 대한 OUTPUT 허용
- 보안 에이전트(EDR)에서 메타데이터 서비스 차단 정책이 있는지 확인
예시(iptables):
sudo iptables -I OUTPUT -d 169.254.169.254/32 -p tcp --dport 80 -j ACCEPT
원인 3) 커스텀 라우팅(UDR) / 정책 라우팅으로 로컬 링크 경로가 꼬임
Azure에서 UDR(User Defined Route)을 강하게 적용하거나, VM 내부에서 정책 라우팅을 구성한 경우 링크-로컬 트래픽이 예상과 다르게 처리될 수 있습니다.
Linux에서 라우팅 확인
ip route get 169.254.169.254
ip rule show
ip route show table all | sed -n '1,120p'
정상이라면 “로컬 링크” 성격에 맞는 경로가 잡혀야 합니다. 특정 게이트웨이로 나가려 한다면 의심해야 합니다.
해결 방향
- VM 내부 정책 라우팅(rule/table)에서 169.254.169.254 예외를 명시
- Azure 네트워크 레벨에서 강제 터널링(Forced Tunneling) 구성 시, IMDS 예외 라우팅이 필요한지 검토
> 이 파트는 환경마다 정답이 달라 “규칙 1개 추가”로 끝나지 않는 경우가 많습니다. 핵심은 ip route get 169.254.169.254 결과를 기준으로 “어디로 보내는지”를 먼저 확정하는 것입니다.
원인 4) 컨테이너(Docker/Kubernetes)에서 호스트 IMDS 접근이 막힘
VM에서는 되는데 컨테이너에서만 안 된다면, 대개 네트워크 네임스페이스/브리지/정책 문제입니다.
Docker에서 재현 확인
docker run --rm curlimages/curl:8.5.0 \
curl -sS -D - -H "Metadata: true" \
"http://169.254.169.254/metadata/instance?api-version=2021-02-01" | head
- 컨테이너에서만 타임아웃이면
- Docker 네트워크가 링크-로컬로 라우팅하지 못하거나
- 프록시 환경변수가 컨테이너에 주입되어 있거나
- 조직 보안 정책으로 컨테이너의 메타데이터 접근을 차단했을 수 있습니다.
해결 방향
- 컨테이너에
NO_PROXY=169.254.169.254주입 - CNI/네트워크 정책에서 링크-로컬 예외
- (가능하면) Azure SDK의 Managed Identity 엔드포인트를 직접 타지 않고 워크로드 아이덴티티/별도 인증 경로를 설계(환경에 따라)
원인 5) 요청 형식 오류(헤더/버전/파라미터)
네트워크는 정상인데 400/404가 난다면 호출 자체가 잘못된 경우가 많습니다.
자주 하는 실수
Metadata: true헤더 누락 → 400api-version누락/오타 → 400/404- 토큰 요청에서
resource인코딩 문제
최소 정답 템플릿
curl -sS -H "Metadata: true" \
"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https%3A%2F%2Fmanagement.azure.com%2F" \
| jq .
원인 6) Managed Identity 미설정(또는 권한 부족)
IMDS는 열려 있어도, 시스템 할당 ID/사용자 할당 ID가 VM에 붙어있지 않으면 토큰 요청은 실패합니다.
진단 포인트
- VM에 System-assigned Managed Identity가 활성화되어 있는지
- User-assigned를 쓴다면 클라이언트 ID(또는 리소스 ID)를 올바르게 지정했는지
- 토큰을 받아도 실제 Azure API 호출에서 403이 난다면 RBAC 역할이 없는 것
(애플리케이션 관점) Azure SDK에서의 확인 예시
아래는 Java에서 DefaultAzureCredential로 토큰을 받는 단순 예시입니다. IMDS가 막혀 있으면 여기서 타임아웃/예외가 납니다.
import com.azure.identity.DefaultAzureCredential;
import com.azure.identity.DefaultAzureCredentialBuilder;
import com.azure.core.credential.AccessToken;
import com.azure.core.credential.TokenRequestContext;
public class ImdsTokenTest {
public static void main(String[] args) {
DefaultAzureCredential cred = new DefaultAzureCredentialBuilder().build();
TokenRequestContext ctx = new TokenRequestContext()
.addScopes("https://management.azure.com/.default");
AccessToken token = cred.getToken(ctx).block();
System.out.println(token.getToken().substring(0, 20) + "...");
}
}
- 여기서 실패하면 SDK가 IMDS를 못 치는 것이므로, 위에서 다룬 프록시/라우팅/방화벽을 먼저 의심합니다.
실전 트러블슈팅 순서(추천)
현장에서 시간을 가장 아끼는 순서로 정리하면 다음과 같습니다.
- VM 로컬에서 curl로 IMDS 메타데이터 200 OK 확인
- 같은 VM에서 토큰 엔드포인트 호출로 200/401/403/400 분기
- 타임아웃이면 즉시:
HTTP_PROXY/HTTPS_PROXY/NO_PROXY확인 및NO_PROXY=169.254.169.254적용tcpdump로 SYN이 나가는지 확인- iptables/nft 규칙 검색
- VM에서는 되는데 컨테이너만 안 되면:
- 컨테이너 env의 프록시 변수 확인
- 네트워크 모드/정책 점검
- 401/403이면:
- VM에 Managed Identity 할당 여부
- User-assigned면 clientId 지정 여부
- RBAC 역할 부여 여부(토큰 발급과 권한은 별개)
자주 쓰는 “원인-해결” 요약표
| 증상 | 유력 원인 | 1차 조치 |
|---|---|---|
| 타임아웃 | 프록시/NO_PROXY 누락 | NO_PROXY=169.254.169.254 적용 |
| 타임아웃 | 방화벽/iptables 드롭 | OUTPUT 허용, tcpdump로 확인 |
| 타임아웃 | 라우팅/정책 라우팅 | ip route get 169.254.169.254 확인 후 예외 라우트 |
| 400/404 | 헤더/버전/URL 오류 | Metadata:true, api-version 재확인 |
| 401/403 | MI 미할당/파라미터 오류 | Identity 활성화, resource/scope 확인 |
| VM OK, 컨테이너 FAIL | 컨테이너 프록시/네트워크 | 컨테이너 env/네트워크 정책 점검 |
마무리: “IMDS는 네트워크가 아니라 로컬 경로”로 접근하자
169.254.169.254는 일반적인 외부 API 호출처럼 보이지만, 실제로는 플랫폼으로 연결되는 로컬 특수 경로입니다. 그래서 프록시, 보안 룰, 라우팅 변경 같은 “조직 표준 설정”이 얹히는 순간 가장 먼저 깨지는 지점이 되곤 합니다.
위의 순서대로 curl 기준을 먼저 세우고(정상/비정상), 프록시 → 방화벽 → 라우팅 → 컨테이너 → ID 설정으로 범위를 좁히면, 대부분의 IMDS 접근 실패는 짧은 시간 안에 원인을 확정할 수 있습니다.
추가로, 장애를 체크리스트로 분해해 재현/격리하는 방식이 익숙하지 않다면 Argo CD Sync 실패 - OutOfSync·Degraded 해결법처럼 “증상→분기→확인 명령→조치” 형태의 글도 함께 읽어두면 운영 효율이 확실히 올라갑니다.