Docker 호스트를 운영하면서 저는 Watchtower를 통해 내부적으로 컨테이너 버전을 자동으로 업그레이드하고 있습니다. 그리고 Watchtower는 별다른 설정없이도 편리하게 Docker 컨테이너들의 업데이트를 주기적으로 확인하고 적용해줍니다. 그러나 모든 컨테이너를 기본적으로 Docker 네트워크의 DNS로 관리하다보면 컨테이너가 재생성되고 새로운 IP를 할당받았을 때 Nginx가 자체적 캐시를 업데이트하지 못하여 HTTP 502 오류가 우발적으로 발생하게 됩니다.

Docker 컨테이너와 Nginx

Docker 컨테이너를 만들고 내부적으로는 External network와 container_name 값을 통해서 관리가 됩니다. 그래서 실질적으로는 직접적으로 Compose에서 연결해주지 못하고 다음과 같은 구조를 가지게 됩니다.

Docker Host [
  Nginx (External) <--- External Network (so) ---> [
    Container { name: typed-sh } [
      Nginx (Internal, Optional) <-> App (HTTP)
    ]
  ]
]

초기 설정 이후 Nginx는 시작할 때 시스템 DNS resolver를 사용해 Typed.sh 컨테이너의 External Network IP주소를 조회하여 연결하는데 중간에 Typed.sh 컨테이너가 재생성되면서 IP주소가 바뀌면 그 이후 Nginx에서 이를 알아채지 못하고 이전 IP주소로 계속해서 연결하는 사태가 발생하게 됩니다.

해결책들과 그 사용성

생각한 해결책들은 사실 정말로 많지만 결과만 먼저 말하자면 Nginx의 resolver 설정이 가장 깔끔하였습니다.

Traefik/HAProxy

Nginx 자체를 Traefik이나 HaProxy로 대체할 수 있다는 의견도 받았습니다. 물론 이를 바꾸었을 때는 더욱 유연한 대처가 가능해집니다. 하지만 옮길 시간의 코스트와 성능에서 꽤 많은 차이가 있기 때문에 이는 적절하지 못하다고 볼 수 있습니다. 벤치마크의 경우에는 아래 웹 사이트를 참조하였습니다.

또한 Nginx는 로드밸런서가 아니라 기본적으로 '웹 서버'에 초점을 맞추어 개발된 소프트웨어로 더 많은 기능을 편리하게 제공할 수 있는 역량 또한 가지고 있습니다. 이를 고려했을 때 여전히 가장 빠른 성능을 제공하는 소프트웨어는 Nginx라고 볼 수 있습니다. 또한 애플리케이션 개발을 동시에 하고 있는 입장에서 하나의 웹 서버로 로드밸런싱까지 완성할 수 있다는 것은 큰 장점입니다.

GitHub - NickMRamirez/Proxy-Benchmarks: Benchmarks for several proxies
Benchmarks for several proxies. Contribute to NickMRamirez/Proxy-Benchmarks development by creating an account on GitHub.

Watchtower Lifecycle Hook

먼저 Watchtower와 관련해서 Nginx 컨테이너를 재시작하거나 하는 방안을 탐색하였습니다. 그리고 실제로 Watchtower에서는 라이프사이클 훅이 존재하고 쉘 스크립트를 이를 시도할 수 있습니다.

하지만 일단 깔끔하지 않습니다. Docker Socket이 연결되어 있음에도 불구하고 이는 아키텍쳐 상 Watchtower에서 컨테이너 재시작을 제공하지 않고 이를 직접 구현하는 것은 한 프로젝트마다 해주어야 하는 작업은 아닐지라도 호스트 자체를 건드리게 됩니다. 이는 이후 서버 이전 작업 등에 다시 같은 문제점이 발생할 수 있고 프로젝트 경로 등 환경에 큰 영향을 받는 해결책이므로 지양해야 합니다.

물론 그럴 일은 없겠지만 이후에 Watchtower에서 이에 관련해서 직접적인 해결책을 제시할 경우에는 주기적으로 확인해야 하는 Nginx의 방법보다 훨씬 효율적인 해결책이 될 수 있을 것입니다.

Lifecycle hooks - Watchtower

Nginx resolver valid period

그 외에도 Nginx에서 DNS 캐시 유효 기간을 지정할 수 있도록 하는 설정을 볼 수 있었습니다. 이 유효 기간을 적절히 설정하여 저희는 컨테이너 재생성 이후에도 지속적으로 컨테이너에 Nginx가 올바르게 접근할 수 있도록 할 수 있습니다. 컨테이너 갯수나 환경에 따라 적절한 유효 시간 자체는 달라지겠지만 짧은 유효 시간은 대규모 컨테이너 운용 환경에서 시스템에 영향을 줄 수 있으니 주의해야 합니다.

Typed.sh와 같은 경우에는 정적 페이지가 생성되는 블로그이기 때문에 그렇게 짧은 유효 시간 자체는 필요하지 않습니다. 또한 자체적으로 Nginx가 캐싱 작업을 하고 있어 실제 호스트가 다운되더라도 더 많은 시간을 벌 수 있습니다. 설정과 같은 경우에는 전역적으로 적용을 위해 HTTP 블록에 추가하였습니다.

http {
  resolver 127.0.0.11 valid=2s;
}

Resolver 주소와 같은 경우는 Docker 컨테이너에 접근하여 /etc/resolv.conf에서 직접적으로 얻으실 수 있으니 참고하시길 바랍니다.