
이 게시물은 2014년 10월 nginx.conf에서 Belly Card의 Kevin Reedy가 진행한 발표를 바탕으로 작성되었습니다. 발표 후 몇 년이 지났지만, 그 내용은 오늘날에도 여전히 매우 유용합니다. 발표 녹화 영상은 YouTube에서 시청하실 수 있습니다.
목차
0:00 소개 개요 면책 조항1:16 Belly란? Belly의 기술 스택1:44 버전 0: 암흑기2:04 버전 1: 새로운 희망2:35 버전 2: 홈페이지와 API 분리 오늘의 유행어: 서비스 지향 아키텍처! SOA 활성화 ELB를 대체하는 NGINX 업스트림 및 가상 서버 구성 결과4:43 Chef를 사용한 구성 레시피 파일 템플릿 쿡북6:49 예시: NGINX용 Chef 설치, 구성 및 튜닝 API 트래픽 라우팅9:46 실제 환경의 Chef 예시10:08 버전 2: 앱 배포10:40 버전 3: 모든 것을 SOA로 그리고 Heartbleed가 발생했다 모든 것에 SSL 적용12:25 버전 4: Docker 이점 컨테이너 배포15:45 배포 전략17:43 서비스 디스커버리 옵션18:33 Consul 아키텍처19:30 Consul 기능22:07 Consul Template Consul Template 시작 명령어 Consul Template을 사용한 NGINX 구성 Consul Template을 사용한 구성 관리24:35 Chef를 사용하여 Consul 배포25:44 전문가 팁 Chef 부분 검색 DNS 오래된 구성 방지30:35 질문36:31 연락처 정보
0:00 소개

여러분, 안녕하세요. 저는 Kevin Reedy이고, 시카고에 본사를 둔 Belly Card라는 회사에서 일하고 있습니다. 저희 회사에는 약 100명의 직원과 20명의 엔지니어가 있습니다. 오늘은 끊임없이 변화하는 동적 인프라에서 저희가 로드 밸런싱을 어떻게 수행하는지 말씀드리겠습니다.
0:21 개요

먼저 제가 ‘암흑기’라고 부르는 버전 0부터 시작해, 시간의 흐름에 따라 저희 인프라 스택이 어떻게 변화해 왔는지 말씀드리겠습니다. 그런 다음 Chef를 사용해 NGINX를 구성하는 방법을 살펴보겠습니다. 이어서 기존 방식에서 벗어나 Consul이라는 서비스 디스커버리 플랫폼의 데이터를 사용해 NGINX를 구성하도록 어떻게 전환했는지 이야기하겠습니다. 시간이 허락한다면, 특히 구성 관리 측면에서 NGINX를 구성할 때 유용한 몇 가지 전문가 팁도 알려드리겠습니다.
0:49 면책 조항

이제부터 Chef와 Consul에 관해 이야기하겠지만, 먼저 이 발표의 내용은 이 두 제품에만 국한되지 않는다는 점을 말씀드리고 싶습니다. Chef, Puppet, Ansible, Salt, CFEngine을 비롯한 모든 제품은 훌륭하며, 여러분도 그중 하나를 사용하셔야 합니다. 저희는 Chef를 즐겨 사용하고 있고, 다른 많은 분도 마찬가지일 거라고 확신합니다.
서비스 디스커버리도 마찬가지입니다. Consul, etcd, ZooKeeper 모두 훌륭하니 여러분에게 잘 맞는 것을 사용하세요.
1:16 Belly란 무엇인가요?

Belly란 무엇일까요? 쉽게 말해, 커피숍에서 음료를 열 잔 사면 한 잔을 무료로 주는 종이 쿠폰 카드를 디지털로 대체한 서비스입니다. 매장에 iPad를 설치하면 고객은 휴대폰이나 QR 코드가 있는 실물 카드를 스캔하고, 저희는 포인트를 적립·관리하고 리워드를 제공하며, 이를 기반으로 마케팅 도구까지 구축합니다.
1:36 Belly의 기술 스택
하지만 저는 여기서 기업으로서의 belly에 대해 이야기하려는 것이 아닙니다. 그 내용이 궁금하시다면 저희 웹사이트를 방문하시면 됩니다. 저는 저희의 인프라 스택에 대해 이야기하겠습니다.
1:44 버전 0: 암흑기
먼저 버전 0입니다. 개발자가 세 명뿐이었고 제가 합류하기 전, Belly는 모놀리식 Ruby on Rails 애플리케이션이었습니다. 즉, 모든 API 호출과 웹사이트의 모든 부분이 하나의 Rails 앱으로 전달되었습니다. MySQL, MongoDB, Memcached를 기반으로 했으며, 시작하기에 아주 좋은 플랫폼인 Heroku에 배포했습니다.
2:04 버전 1: 새로운 희망
버전 1, 새로운 희망입니다. Heroku로는 더 이상 우리의 요구 사항을 충족할 수 없다고 판단해 Capistrano라는 도구를 사용해 배포하기 시작했습니다. Capistrano는 Ruby로 작성된, 본질적으로는 ‘셸 스크립트보다 나은’ 도구로, 애플리케이션 배포를 도와줍니다.
Amazon Web Services EC2에 서버가 약 6대 있었습니다. 서버 수는 기본적으로 고정되어 있었지만, 큰 이벤트가 예정되어 있으면 늘렸다가 필요에 따라 다시 줄이곤 했습니다. 그 앞단에는 AWS Elastic Load Balancer를 두었습니다.
2:35 버전 2: 홈페이지와 API 분리
그러던 중 홈페이지를 나머지 API와 분리하는 프로젝트를 진행하게 되었습니다. 홈페이지를 더 빠르게 개선하고 여러 개발자가 각자 작업할 수 있도록 하기 위해서였습니다.
2:47 오늘의 유행어: 서비스 지향 아키텍처!
당시에도, 그리고 오늘날에도 가장 큰 화두는 서비스 지향 아키텍처입니다.
2:50 SOA 구현
그래서 저희 스택의 버전 2는 서비스 지향 아키텍처를 지원하도록 설계되었습니다. 첫 번째 계획은 홈페이지와 API를 분리하는 것이었습니다. 아주 간단해 보입니다. www.bellycard.com과 api.bellycard.com만 있으면 되니까요.
하지만 일부 모바일 애플리케이션이 다음 주소를 사용하도록 코드에 고정되어 있었기 때문에, 실제로는 전혀 간단하지 않고 매우 복잡한 일이었습니다. www.bellycard.com/api 및 /api‑assets 이메일 서비스 제공업체와 이미 사람들에게 발송된 이메일의 모든 클릭 링크에는 ‘이메일 열람’을 위한 /eo와 ‘이메일 클릭’을 위한 /ec가 포함되어 있었습니다. Facebook 앱은 다음 경로를 사용하도록 설정되어 있었습니다. /facebook/callback.
3:28 NGINX가 ELB를 대체하다
이는 AWS Elastic Load Balancer로는 더 이상 이 트래픽을 여러 애플리케이션으로 라우팅하는 데 필요한 유연성을 확보할 수 없다는 뜻이었습니다. 이때 NGINX가 해결책으로 등장했습니다. 구체적으로는 여러 업스트림과 재작성 규칙을 사용해 트래픽을 각기 다른 애플리케이션으로 라우팅할 수 있었습니다.
3:44 업스트림 및 가상 서버 구성
이 매우 기본적인 예제에서는 API용 서버 6개가 포함된 upstream 블록과 홈페이지용 서버 2개가 포함된 블록을 정의합니다.
또한 가상 서버를 정의하는 server 블록이 두 개 있습니다. 하나에서는 api.bellycard.com 요청을 수신하여 모든 트래픽을 API 서버로 라우팅합니다. 다른 하나에서는 www.bellycard.com 요청을 수신하여 적절하게 라우팅합니다.
4:02 결과
이 방식은 매우 효과적이었습니다. 덕분에 개발자들이 빠르게 작업할 수 있었습니다. 프런트엔드 담당자들은 하나의 프로젝트에서 작업하면서도 API 때문에 지연되는 일이 없었습니다. 그래서 세 번째 서비스를 배포했습니다. 아마 Apple의 Passbook과 연동하기 위한 서비스였던 것으로 기억합니다. 이어서 네 번째 서비스로 Facebook 관련 기능을 모두 분리했습니다. 다섯 번째 서비스는 웹사이트의 사용자 이벤트를 처리하기 위한 것이었습니다.
그러다 누군가 그걸 Node.js로 작성하면 아주 좋겠다고 생각했고, 그 결과 두 가지 언어로 작성된 서로 다른 서비스가 다섯 개나 생겼습니다. 이 서비스들은 별다른 구성 관리 없이 각각 독립적으로 배포되었고, NGINX도 수동으로 편집하고 있었습니다. 아마 많은 분이 처음에는 이렇게 시작하셨을 겁니다.
4:43 Chef를 사용한 구성 관리
이 프로세스를 더 효과적으로 확장할 방법이 필요했고, 이를 위해 Chef를 도입했습니다.
Chef는 구성 관리 도구 모음입니다. Ruby로 작성되었으며 실제 구성 관리 코드도 Ruby로 작성할 수 있어 개발자에게 매우 유용합니다. learnchef.com은 Chef를 학습하기에 훌륭한 자료입니다. 이 사이트를 통해 제가 45분 동안 설명할 수 있는 것보다 훨씬 더 많은 내용을 배울 수 있습니다.
그래도 모두가 같은 내용을 이해할 수 있도록 몇 가지 기본 사항을 짚고 넘어가겠습니다.
5:17 레시피
Chef의 첫 번째 구성 요소는 레시피입니다. Chef에서는 도구를 나이프라고 부르고, 레시피를 비롯한 모든 것을 쿡북에 담는 등 재치 있는 이름을 많이 사용합니다. 덕분에 재미있고 이해하기 쉬우며 훌륭한 비유가 되지만, 관련 내용을 Google에서 검색하기는 매우 어렵습니다.
제가 생각할 수 있는 가장 기본적인 레시피는 다음과 같습니다. nginx 패키지를 설치하고 nginx 서비스를 시작한 후 활성화합니다.
5:56 파일
파일 리소스를 사용할 수도 있습니다. 예를 들어, Hello World!라는 내용을 /usr/share/NGINX/html/index.html 파일에 작성하려고 합니다.
6:08 템플릿
이를 템플릿 시스템에 적용할 수도 있습니다. 여기서는 g와 w라는 두 변수를 각각 Hello와 Kevin에 사용하며, 이 변수들을 ERB 형식(Ruby 템플릿 스크립트)의 템플릿 파일에 전달할 수 있습니다.
따라서 콘텐츠를 일일이 직접 작성하는 대신, 읽을 내용과 인사할 대상을 지정하는 템플릿을 작성할 수 있습니다.
6:19 쿡북
마지막 주요 구성 요소는 레시피, 파일, 템플릿의 모음인 쿡북입니다. 이 밖에도 더 복잡한 리소스인 리소스, 프로바이더, 라이브러리가 포함됩니다. 다음 사이트에는 수많은 오픈 소스 및 기타 쿡북이 있습니다. https://supermarket.getchef.com 여기에는 NGINX, MySQL, Java, apt 및 yum용 쿡북은 물론, ssh_known_hosts 파일을 관리하는 쿡북까지 포함되어 있습니다. 구성 관리에서 구현하려는 작업에 필요한 주요 구성 요소는 대부분 이미 쿡북으로 제공됩니다.
6:49 예시: NGINX용 Chef
Chef를 사용해 NGINX를 설치하고 몇 가지 변수를 변경하여 구성하는 간단한 예제를 빠르게 살펴보겠습니다. Chef를 사용하여 애플리케이션이 실행 중인 위치를 찾은 다음, 트래픽이 애플리케이션으로 라우팅되도록 NGINX를 구성하겠습니다.
7:14 설치, 구성 및 튜닝
이 예제에서 가장 먼저 할 일은 몇 가지 노드 속성을 설정하는 것입니다. 각 노드에 JSON 문서를 추가하여 속성 값을 설정할 수 있습니다. 여기서는 이러한 속성의 설정을 참조하는 타사 NGINX 쿡북을 사용하겠습니다. 설정할 항목은 worker_connections의 특정 값, worker_rlimit_nofile 지시문, NGINX에서 사용할 event 유형, 그리고 client_max_body_size까지입니다.
그런 다음 NGINX 쿡북의 레시피 두 개를 포함합니다. 첫 번째는 nginx::repo로, 운영 체제에 따라 apt 또는 yum 리포지토리를 설정합니다. 이어서 [nginx 레시피]가 NGINX를 설치합니다. 소스에서 NGINX를 설치할 수도 있지만, 패키지를 사용하는 편이 더 빠릅니다.
다음으로 Chef의 검색 기능을 활용합니다. Chef를 실행할 때는 머신에서 에이전트로 실행하거나, 기본적으로 스크립트처럼 순차적으로 실행되는 솔로 모드로 실행할 수 있습니다. 머신에 에이전트가 있으면 Chef 서버에 노드와 기타 속성을 쿼리할 수 있습니다.
여기서는 Chef에 belly‑api 레시피가 연결된 모든 노드의 목록을 요청합니다. 그런 다음 /sites‑enabled/api라는 사이트 파일을 템플릿으로 생성합니다. 이 파일의 소스는 다음에 보여드릴 [api.erb라는 파일] 템플릿입니다. 여기에 전달하는 변수는 바로 앞에서 검색한 노드들입니다.
여기서 마지막으로 새롭게 등장하는 것은 notifies 문으로, nginx 서비스를 다시 로드합니다. 즉, 이 템플릿이 변경될 때마다 이 서비스를 다시 로드하라는 뜻입니다.
8:54 API 트래픽 라우팅
여기서 사용하는 템플릿 파일은 매우 간단합니다. api라는 업스트림이 있고, ERB에서 변수로 전달한 모든 서버를 순회하면서 각 서버의 IP 주소와 포트 8080을 지정합니다. 서버 이름이 포함된 주석도 추가합니다. 운영자가 이 서비스의 상황을 파악할 때 유용한 주석이 될 것입니다.
또한 server 블록을 정의하여 api.bellycard.com에서 수신하도록 합니다. 모든 트래픽은 api 업스트림으로 전달하기만 하면 됩니다.
9:46 실제 Chef 활용 예시
실제 사례도 하나 보여드리겠습니다. 글씨가 너무 작아서 읽기 어려울 수도 있지만, Chef 레시피가 얼마나 복잡해질 수 있는지 대략 감을 잡으실 수 있을 겁니다. 이것은 현재 운영 중인 일부 프로덕션 서버에서 실제로 사용하는 템플릿과 레시피입니다.
10:08 버전 2: 앱 배포
이제 다시 저희 스택 이야기로 돌아가겠습니다. 앞서 애플리케이션을 약 5개 배포했고 아주 순조롭게 운영되고 있다고 말씀드렸습니다. 이제 이 애플리케이션들로 트래픽을 라우팅할 방법도 마련되자 개발자들이 이를 적극 활용하기 시작했고, 현재는 Chef를 사용해 서버에 배포한 Ruby 애플리케이션이 약 40개에 이릅니다. 트래픽은 NGINX를 통해 완전히 동적으로 라우팅됩니다.
개발자들은 새 서비스의 이름을 포함하도록 레시피 하나만 수정하면 됩니다. 모든 트래픽은 NGINX를 통해 라우팅되며 Chef를 사용해 동적으로 구성됩니다.
10:40 버전 3: 모든 것을 SOA로
또한 정적 파일로만 구성된 프런트엔드 JavaScript 애플리케이션이 약 10개 있었는데, S3에 배포하고 그 앞단에 CDN인 [Amazon] CloudFront를 두었습니다. index.html은 S3 버킷에서 제공하고, 나머지 에셋은 CloudFront를 통해 제공했습니다. 이 밖에도 개발자들이 일회성으로 만든 뒤 Heroku에 바로 올린 앱이 약 5개 더 있었는데, 결국 프로덕션 환경에서 사용하게 되었지만 그 사실을 제대로 전달받은 사람은 아무도 없었습니다.
11:10 그리고 Heartbleed 사태가 발생했습니다
[Heroku의 다섯 개 앱]은 서로 분리되어 있었고, 저희 인프라와도 완전히 동떨어져 있었습니다. 그러다 4월의 어느 화창한 날, Heartbleed 버그가 발표되면서 상황이 달라졌습니다. 많은 사람이 다급히 대응에 나섰습니다. Heartbleed는 누군가가 사용자의 개인 키를 추출할 가능성이 있는 OpenSSL의 버그였습니다.
Chef를 사용하고 있었기 때문에 패치가 제공되자마자 모든 서버의 OpenSSL 인프라를 업그레이드할 수 있었습니다. 전체 인프라에 배포하는 데 약 20분이 걸렸던 것으로 기억합니다. 하지만 AWS(따라서 Heroku도)는 Elastic Load Balancer를 패치하는 데 약 24시간이 걸렸습니다.
이 일을 계기로 저희는 실제로 모든 로드 밸런싱과 SSL 종료를 배포 위치가 S3든 Heroku든 상관없이 NGINX로 이전했습니다. 이로 인해 약간의 성능 저하가 발생하지만, 적어도 그해 4월의 그날만큼은 저희에게 충분히 감수할 만한 가치가 있었습니다.
11:57 모든 것에 SSL 적용
그래서 저희 스택의 버전 3은 “모든 것을 SOA로”에서 “모든 것을 SSL로”로 바뀌었습니다. bellycard.com과 그 하위 도메인으로 향하는 모든 트래픽은 저희 로드 밸런서를 통과합니다. 성능을 위해 정적 자산은 여전히 다른 도메인의 CloudFront에서 제공됩니다. SSL로 인한 오버헤드는 원하지 않으며, 저희는 동부 해안에 단일 데이터 센터만 운영하고 있기 때문입니다.
12:25 버전 4: Docker
버전 4 – 올해 모두가 이야기하는 화두는 Docker입니다. Docker가 아마 버전 0.5였을 때부터 저희 개발자들은 이를 사용해 보고 싶어 했습니다.
12:38 이점
[Docker를 사용해야 하는] 이유는 간단합니다.
애플리케이션 배포가 훨씬 간단해집니다. 작성해야 할 레시피도 없습니다. 애셋을 어떻게 컴파일할지 걱정할 필요도 없습니다. 다른 사람이 Python으로 앱을 작성하더라도 여러분이 Ruby로 앱을 작성하는 데는 아무런 문제가 없습니다.
본질적으로 개발과 운영의 책임을 분리합니다. 개발자는 코드와 사용할 라이브러리, 심지어 설치할 Linux 패키지까지 신경 쓰면 됩니다. 운영팀은 어디에 배포할지, 로그를 어떻게 수집할지, 어떻게 모니터링할지와 같은 문제에 집중할 수 있습니다. 이러한 운영상의 문제를 해결하고 나면 어떤 언어로 작성된 애플리케이션이든 모두 정상적으로 작동합니다.
또 다른 장점은 개발자들이 각자의 노트북에서 종속 서비스를 손쉽게 구동할 수 있다는 것입니다. 앞서 말씀드렸듯이 현재 약 55개의 애플리케이션을 운영하고 있으며, 그중 상당수가 이제 Docker 컨테이너에서 실행되고 있습니다.
이메일 캠페인을 발송하는 새로운 서비스를 개발 중이라고 해 보겠습니다. 이 서비스는 이메일 서비스에 의존합니다. 따라서 일반적으로 개발 머신에서 코드를 다운로드하고 bundle install을 실행한 다음 백그라운드에서 서비스를 시작합니다. 그러고 나면 “아, 그런데 이 서비스는 또 다른 서비스인 사용자 서비스에 의존하네”라는 사실을 알게 됩니다.
그 서비스는 또 다른 여러 요소에 의존합니다. 따라서 실제로 새로운 이메일 캠페인 서비스는 약 5개의 다른 서비스뿐 아니라 MySQL, Elasticsearch, Redis 등에 의존합니다.
Docker를 사용하면 프로덕션 환경에서 사용하는 것과 동일한 Chef 레시피로 해당 컨테이너를 빌드하고, 개발자들이 매우 빠르게 다운로드할 수 있는 컨테이너를 제공할 수 있습니다.
따라서 배포와 개발 속도가 빨라집니다.
14:23 컨테이너 배포
그래서 버전 4에서는 Chef를 사용해 애플리케이션을 배포하던 방식에서 컨테이너에 배포하는 방식으로 전환했습니다. 이를 위한 솔루션은 매우 다양하며, 앞으로 더 많은 솔루션이 등장할 것이라고 확신합니다.
첫 번째는 CoreOS와 Fleet입니다. CoreOS는 간소화된 Linux 운영 체제로, 그 위에서 etcd라는 서비스 검색 엔진을 실행합니다. Fleet을 사용하면 “컨테이너 하나를 5개 실행하되, 이 다른 서비스와 동일한 머신에서는 실행하지 않겠다”와 같은 조건을 정의할 수 있습니다.
또 다른 도구로는 Git Push 인터페이스를 제공한다는 점에서 Heroku와 비슷한 Deis가 있습니다. Apache의 Mesos와 Google의 Kubernetes도 있고, 최근에 출시된 Flynn도 있습니다. 저희가 이 여정을 시작했을 당시에는 이러한 도구 대부분이 아직 실제로 사용할 수 있는 수준에 이르지 못했기 때문에 자체 도구를 개발했습니다.
저희가 만든 것은 Jockey라고 하는데, 배포 프로세스가 매우 명확한 원칙에 따라 설계되어 있어 아주 훌륭하게 작동하고 있습니다. Docker의 장점은 기본적으로 컨테이너화를 위한 공통 API 세트를 제공한다는 점입니다. Linux의 LXC 컨테이너만 사용했다면 신경 써야 했을 많은 부분을 걱정하지 않아도 됩니다.
이를 오픈 소스로 공개할 계획이지만, 앞서 말씀드렸듯이 배포 방식은 매우 주관적이어서 저희에게 잘 맞는 방식이 여러분에게는 맞지 않을 수도 있습니다.
15:45 배포 전략
또한 Docker로 전환하면서 배포 전략도 다소 바뀌었습니다. 이전에는 애플리케이션의 새 버전을 기존 환경에 그대로 배포하는 방식으로 무중단 배포를 수행했습니다. 예를 들어 사용자 팩스 서비스가 네 대의 머신에서 실행 중이라면, Chef를 사용해 새 버전을 내려받아 [이전 버전] 위에 설치한 다음 웹 서버에 리로드 신호를 보냈습니다. Unicorn은 이런 작업에 매우 적합한 Ruby 엔진입니다.
하지만 컨테이너는 변경할 수 없습니다. 실행 중인 컨테이너가 있는 상태에서 새 빌드를 만들려면 새 컨테이너를 만들어야 합니다. 컨테이너에 접속해 추가 명령을 실행한 다음 저장할 수도 있습니다. Git에서 하는 것과 비슷한 방식입니다. 하지만 Docker를 최대한 활용하려면 컨테이너를 완전히 변경 불가능한 상태로 유지해야 합니다.
따라서 더 이상 기존 환경에 그대로 배포할 수 없게 되었기 때문에 새로운 배포 전략을 마련해야 했습니다. 사용할 수 있는 전략은 몇 가지가 있는데, 그중 블루‑그린 배포가 흥미로운 방식입니다. 예를 들어 애플리케이션이 네 대의 머신에서 실행 중이라면, 새로운 머신 네 대에 애플리케이션을 구동한 다음 로드 밸런서가 새 머신을 가리키도록 전환합니다. 애플리케이션이 예상대로 제대로 작동하는지 확인한 후에는 블루 환경을 제거할 수 있으며, 그러면 그린 환경이 새로운 블루 환경이 됩니다. Amazon은 AWS에서 이 작업을 수행할 수 있는 매우 훌륭한 도구를 제공합니다.
또한 새 머신 버전 하나를 먼저 배포한 뒤 모니터링하고, 메트릭 검사와 상태 검사를 통과하면 배포를 계속 진행하는 ‘카나리’ 배포 방식도 있습니다.
여기서 컨테이너의 차이점은 기존 컨테이너를 실제로 종료하고 새 컨테이너를 가동한다는 것입니다. 따라서 인프라에서 실행 중인 각 애플리케이션의 위치를 추적할 수 있는 일종의 시스템이 필요합니다.
실제로 Chef는 이 작업을 수행하는 매우 좋은 방법으로 자리 잡기 시작했습니다. 현재 병합 중인 Chef Metal이라는 쿡북을 사용하면 Chef 인프라의 다른 리소스와 마찬가지로 컨테이너를 다룰 수 있습니다.
17:43 서비스 디스커버리 옵션
서비스 디스커버리도 있습니다. ZooKeeper는 Hadoop 전반에서 널리 사용되는 매우 인기 있는 도구입니다. [Airbnb]에는 SmartStack이라는 훌륭한 제품이 있는데, 이를 사용하면 배포를 수행하고 ZooKeeper에서 배포 현황을 추적할 수 있습니다.
앞서 etcd를 언급했는데, 이는 기본적으로 REST 인터페이스를 갖춘 분산형 키-값 저장소입니다. Java 클라이언트 라이브러리에 가까운 ZooKeeper와 달리, 이제 어떤 애플리케이션에서든 etcd와 통신할 수 있다는 점이 큰 장점입니다. 앞서 언급한 Deis와 Flynn, 심지어 Google의 Kubernetes 같은 여러 오픈 소스 서비스형 플랫폼(Platform-as-a-Service)도 etcd를 사용합니다. CoreOS를 사용하고 있다면 etcd가 이미 기본으로 내장되어 있습니다.
REST 기반의 또 다른 도구로 Consul이 출시되었습니다. Vagrant와 Packer를 비롯해 수많은 훌륭한 도구를 만든 HashiCorp에서 공개한 것입니다. 저희가 실제로 사용하기로 결정한 것도 Consul입니다.
18:33 Consul 아키텍처
다음은 Consul의 가장 기본적인 구조를 보여 주는 다이어그램입니다. 여러 데이터 센터가 있고 각 데이터 센터에는 서버가 세 대씩 있는 것을 볼 수 있습니다. 데이터 센터 1에는 클라이언트도 있습니다. 흥미로운 점은 서버 에이전트 중에서 기본 에이전트를 선출하며, 모든 클라이언트가 모든 서버뿐만 아니라 다른 모든 클라이언트의 존재도 파악하고 있다는 것입니다. 그런 다음 가십 프로토콜을 사용해 다른 모든 에이전트와 통신합니다.
이것을 보면 두 친구에게 이야기하면 그 친구들이 또 두 친구에게 이야기하고, 그 친구들이 다시 두 친구에게 이야기하면서 메시지가 전체 노드 네트워크로 퍼져 나가는 예전 AT&T 광고가 떠오릅니다. [편집자 주 – 거의 맞습니다. 그 광고는 Faberge Organics 샴푸 광고였습니다.] 이와 달리 etcd나 ZooKeeper는 기본적으로 서버 에이전트만 있고 클라이언트가 여기에 연결하는 방식입니다.
Consul 방식에서는 Docker를 배포하는 모든 곳과 로드 밸런서를 실행하는 모든 곳을 포함해 사실상 거의 모든 서버에서 Consul 클라이언트를 실행합니다. 따라서 언제든지 localhost의 8500번 포트에서 해당 API와 통신할 수 있습니다.
19:30 Consul 기능
etcd와 마찬가지로 [Consul]은 분산 키-값 저장소입니다. 키를 설정하고 가져올 수 있으며, 선형화 가능성을 유지하므로 항상 일관성이 보장됩니다. 몇 가지 주의할 점이 있지만, 문제가 발생할 수 있는 부분을 설명한 문서는 매우 훌륭합니다. 다만 etcd와 달리 서비스 디스커버리를 핵심 기능으로 취급합니다.
단순한 키-값 쌍뿐만 아니라 API 같은 서비스도 등록할 수 있습니다. 서비스를 등록할 때는 해당 서비스가 실행 중인 노드를 Consul에 알려 줍니다. 즉, 컨테이너를 배포할 때 이 호스트에서 실행되는 서비스 API도 등록하도록 Consul에 알립니다.
또한 Consul은 분산 상태 확인 기능을 제공하므로, 이제 서비스 디스커버리 플랫폼 내에서 바로 Nagios 방식의 상태 확인을 정의할 수 있습니다. Consul을 조회할 때 API 서비스가 실행 중인 모든 위치의 목록을 요청할 수 있습니다. 이때 “전체”, “정상인 항목만” 또는 “비정상인 항목만”을 지정하는 플래그를 전달할 수 있습니다.
또한 여러 데이터 센터를 지원합니다. 앞서 Consul 클라이언트를 모든 곳에서 실행해야 한다고 말씀드렸습니다. 클라이언트를 시작할 때 데이터 센터를 지정하는 매개변수를 전달합니다. 동부 데이터 센터와 서부 데이터 센터가 있다면, 데이터 센터를 따로 인식할 필요 없이 로컬 Consul 클라이언트에 주소 등을 요청할 수 있습니다.
실제로 저희는 데이터 센터를 스테이징 환경과 프로덕션 환경을 구분하는 경계로 사용합니다. 스테이징 환경의 애플리케이션 설정에는 “MySQL.services.consul이 어디에 있는지 알려 줘”라고 지정할 수 있습니다. 이 서비스가 실제로 스테이징 노드에서 실행되든 프로덕션 노드에서 실행되든, 데이터 센터나 여러 환경을 인식하도록 구성할 필요 없이 올바른 값을 얻을 수 있습니다.
이것(그리고 etcd)의 또 다른 훌륭한 점은 HTTP 인터페이스에서 롱 폴링을 지원한다는 것입니다. 즉, 이 서비스를 실행 중인 모든 서버의 목록을 쿼리해 요청할 수 있을 뿐만 아니라, 연결을 60초 동안 열어 둔 채 변경 사항이 생기면 즉시 알림을 받을 수 있습니다. 폴링 시간은 최대 10분까지 설정할 수 있습니다. 따라서 인프라에 변경 사항이 발생하면 즉시 알림을 받을 수 있습니다.
앞서 언급했듯이, 곳곳에 클라이언트를 배치하면 에이전트를 확장할 수 있는 기능을 구축할 수 있습니다. 이렇게 하면 언제든 localhost와 통신할 수 있어 매우 유용합니다.
하지만 가장 큰 문제는 이것입니다. 이제 이 데이터가 분산 서비스 검색 플랫폼에 저장되어 있는데, 이 데이터를 바탕으로 NGINX를 어떻게 구성해야 할까요?
22:07 Consul 템플릿
다행히도 Consul Template이라는 도구가 있습니다. 말 그대로 어제 출시되는 바람에 오늘 발표 자료를 이 부분부터 수정해야 했습니다. 예전에는 “Consul HAProxy”라고 불렸는데, 실제로는 HAProxy에만 특화된 도구가 아니었기 때문에 아주 형편없는 이름이었습니다.
Consul Template은 서비스 변경 사항, 해당 서비스의 상태 검사, 심지어 키-값 저장소의 변경 사항까지 구독할 수 있는 템플릿을 만들기 위한 매우 간단한 인터페이스를 제공합니다. 따라서 NGINX 워커 [수]와 같은 설정을 키-값 저장소에 저장해 두었다가 값이 변경되면 구성을 다시 생성할 수 있습니다.
여기서 가장 중요한 기능은 템플릿으로 파일을 생성하는 것으로, 이전에 Chef로 하던 작업과 매우 비슷해 보입니다. 가장 큰 차이점은 Ruby 기반의 ERB 대신 Golang 템플릿 언어를 사용한다는 것입니다. 파일을 변경한 후에는 선택적으로 명령어를 실행할 수 있습니다. 예를 들면 service nginx reload입니다.
22:59 Consul Template 시작 명령어
다음은 명령줄에서 Consul Template을 실행하는 예입니다. 실제 Consul 호스트를 지정하며, 이 경우에는 consul‑prod.example.com입니다. 실제 프로덕션 환경에서는 모든 곳에서 Consul을 실행하고 localhost에 연결하는 방식을 권장합니다.
그런 다음 Golang 템플릿인 템플릿 파일을 전달합니다. 여기서는 /etc/consul‑templates/api를 전달합니다. 그런 다음 실제로 출력할 파일을 지정합니다. 여기서는 /etc/NGINX/sites‑enabled/api입니다. 그다음 호출할 명령어를 지정합니다: service nginx reload.
23:32 Consul Template을 사용한 NGINX 구성
Consul 템플릿은 이전에 보여드린 슬라이드와 매우 비슷하지만, ERB 대신 Golang을 사용합니다. 여기서는 두 개의 업스트림을 정의합니다. 하나는 api이고 다른 하나는 homepage입니다. 서비스 API에는 range 문을 사용하며, 해당 서비스가 실제로 실행 중인 위치의 IP와 포트를 가져오는 기본 제공 변수가 있습니다.
23:54 Consul Template을 활용한 구성 관리
Consul Template을 이런 방식으로 사용할 때의 장점은 서비스 변경 사항이 빠르게 반영된다는 것입니다. Docker로 배포하기 전에는 머신에 따라 약 5분 또는 10분마다 Chef를 실행했습니다. 당시에는 기존 환경에 그대로 배포하고 있었기 때문에 그 정도 속도면 충분했습니다.
이제 더 고도화된 배포 시스템을 구축하고 있으므로, 이러한 변경 사항을 몇 분이 아니라 몇 초 안에 통지받아야 합니다. 실제로는 밀리초 단위로 통지받으며, 평균적으로 약 200밀리초 정도 걸립니다.
하지만 템플릿을 생성하려면 여전히 구성 관리가 필요합니다. Chef를 단순히 Consul로 대체할 수는 없습니다. Consul과 Consul Template을 비롯한 모든 요소를 어떻게 배포할지 여전히 결정해야 합니다.
24:35 Chef를 사용하여 Consul 배포하기
실제로 저희는 Chef 템플릿을 사용해 Consul 템플릿을 배포하고 있는데, 다소 복잡해 보일 수 있습니다. 먼저 Chef 레시피에서 Consul 호스트에 모든 서비스 목록을 요청합니다. 그런 다음 [template 및 source 문을 사용해] Consul 템플릿을 생성하고, SSL 인증서와 키의 위치를 전달한 뒤 consul‑template을 다시 로드합니다. [편집자 주 – Reedy 씨는 처음에 consul‑haproxy를 언급했으며, 이는 슬라이드의 첫 번째 notifies 문에 표시되어 있습니다. 이후 그는 자신의 발언을 정정하고, 이 슬라이드는 Consul Template이 출시되기 전에 만들어진 것이라고 설명했습니다.]
또한 Consul Template용 구성 파일을 작성하고 Consul 호스트, 소스, 대상 및 다시 로드 명령을 [지정하는 변수로] 전달합니다. 이것이 바로 Chef와 Consul을 함께 사용하여 NGINX 구성에 대한 업데이트를 즉시 반영하는 핵심 방식입니다.
25:44 전문가 팁
다음으로 Chef, Consul 및 전반적인 NGINX 구성에 관한 몇 가지 전문가 팁을 알려드리겠습니다.
25:52 Chef 부분 검색
첫 번째 팁은 서비스가 실행 중인 위치를 찾기 위해 Chef 검색을 사용하고 있다면 Chef 부분 검색을 살펴보라는 것입니다. Chef 검색의 문제점은 모든 속성을 포함한 전체 노드의 복사본을 만든다는 것입니다. 노드 속성에 많은 정보를 저장하면 Chef 서버와 통신할 때 해당 정보를 네트워크를 통해 가져와야 할 뿐만 아니라 메모리에도 저장해야 합니다.
Chef 검색에서 수백 또는 수천 대의 서버가 반환된다면 실제 레시피를 실행하는 데 필요한 메모리가 얼마나 쉽게 부족해질 수 있는지 알 수 있습니다. 부분 검색을 사용하면 필요한 키만 반환할 수 있습니다.
예를 들어, 처음에는 다음과 같았습니다.
nodes = search(:node, 'recipe:belly-api')이 검색은 모든 객체에 대한 모든 정보를 반환합니다. 여기에는 디스크 공간, RAM, CPU에 관한 정보와 자체 노드 속성을 기록한 모든 애플리케이션에 관한 정보가 포함됩니다.
대신 부분 검색을 사용할 수 있습니다. 이때 search_keys라는 새 해시를 만들고 필요한 키 목록(이 경우에는 이름과 IP 주소만)을 지정합니다. 그런 다음 이와 매우 유사한 partial_search API를 호출하고 추가 [keys] 매개변수를 전달합니다.
26:53 DNS
S3와 Heroku 앞단에 로드 밸런싱을 도입했을 때 두 번째로 문제가 된 점은 NGINX가 설정을 다시 로드할 때만 DNS를 사용해 upstream 블록의 호스트 이름을 확인한다는 것입니다. 즉, 업스트림 서버를 s3.amazonaws.com 또는 anything.herokuapp.com으로 설정했는데 해당 주소가 변경되면, 다음 설정 재로드 전까지 로드 밸런서는 새 IP 주소를 인식하지 못합니다.
이 문제를 해결하기 위해 실제 DNS 조회는 Chef에서 수행하고, 결과가 변경되면 NGINX를 다시 로드하도록 했습니다. 그러자 또 다른 문제가 생겼습니다. [DNS 응답의 주소 순서]가 항상 같지는 않으므로, 응답을 템플릿에 기록하기 전에 반드시 정렬해야 합니다.
27:36 오래된 설정 방지
수많은 서비스를 배포하기 시작할 때 생기는 또 다른 문제는 오래된 구성이 남지 않도록 해야 한다는 것입니다. [편집자 주 – Reedy 씨가 청중의 질문에 답하기 위해 잠시 발표를 멈춥니다.] Apache, NGINX 및 기타 UNIX 계열 시스템에서는 일반적으로 conf.d 폴더를 만들고 그 안의 모든 파일을 불러오는 구성 패턴을 사용합니다. 따라서 서비스 A, B, C를 배포한다면 service-a.conf, service-b.conf, service-c.conf 파일을 만듭니다.
그런 다음 서비스 B를 D로 교체하기로 했는데, 이제 NGINX 구성 폴더에는 구성 파일이 네 개나 있지만 서비스 B는 더 이상 존재하지 않습니다. B와 D의 호스트 이름이 비슷하기 때문에 서로 충돌할 수도 있습니다.
과거에는 이 작업[오래된 구성 제거]을 두 가지 방식으로 시도했습니다. 템플릿에는 :delete 함수를 추가할 수 있지만, 이 방식은 금세 관리하기가 상당히 까다로워졌습니다. 기본적으로 이전 서비스 목록을 만들어 두고, 해당 서비스가 존재하면 항상 삭제를 시도했습니다. 하지만 이 방식은 규모가 커지면 제대로 대응할 수 없었습니다.
또 다른 방법은 모든 사이트 설정을 하나의 파일로 렌더링하는 것입니다. 많은 사람이 이를 “멱등성을 상위 수준으로 이동한다”라고 표현해 왔습니다. 즉, 여러 소스에서 불러오는 NGINX 설정을 사용하는 대신 모든 설정을 nginx.conf에 넣는 것입니다.
실제로 서버에 로그인해서 작업할 때는 다소 다루기 불편해지지만, 이제는 운영 환경에서 설정 파일을 직접 수정하며 문제를 해결하지 않아도 되는 단계에 이르렀기를 바랍니다. 그래도 Chef 레시피에서 다루기가 여전히 조금 번거롭다면, 저희는 여러 사이트 설정을 불러오는 하나의 nginx.conf 파일을 사용합니다. 예를 들어 사이트 설정 파일 중 하나에는 약 30개의 서비스가 나열되어 있고, Heroku용 파일 하나, 모든 S3 서비스용 파일 하나 등을 별도로 둡니다.
따라서 모든 사이트 설정을 하나 또는 소수의 파일로 렌더링하면 따로 정리할 필요가 없습니다. Chef의 관점에서 어떤 서비스가 사라지면, Chef는 해당 서비스를 실제 파일에 렌더링하는 작업을 중단하기만 하면 됩니다.
30:35 질의응답
Q: 지금까지 Consul을 사용해 보니 어떠셨나요?
A: 저희는 Consul을 아주 만족스럽게 사용해 왔습니다. etcd 같은 솔루션과 비교했을 때 제가 특히 마음에 드는 점은 Consul을 기반으로 한 서비스들이 계속 개발되고 있다는 것입니다. 서비스 디스커버리에 대해 걱정할 필요가 없죠. 실제로 레거시 애플리케이션이 있고, 예를 들어 Consul을 사용해 MySQL 서버를 배포한다면 Consul은 DNS 인터페이스도 제공합니다.
애플리케이션이 Consul을 인식할 필요는 없습니다. 그저 “mysqldb.services.consul에서 어떤 주소를 받을 수 있지?”라고 질의하면 됩니다. 이 경우 Consul이 DNS 서버 역할을 합니다. TLD[최상위 도메인]가 .consul로 끝나면 질의가 Consul로 라우팅되도록 DNS 인프라를 설정합니다.
저희 환경에서는 확장성이 아주 뛰어났습니다. 프로덕션에서 서버 에이전트용 노드 약 3개와 클라이언트 약 60개를 운영하고 있습니다. 지금까지 아무런 문제도 없었습니다. 기능은 계속 추가하면서도 하위 호환성을 깨뜨리지 않아 업그레이드도 늘 순조로웠습니다.
질문: NGINX Plus를 사용하시나요?
A: 사용하지 않습니다. Consul을 사용하기 시작했을 때 바로 이 용도로 검토해 본 적은 있습니다. 템플릿 시스템이 필요하지 않다면 HTTP 롱 폴링을 수신한 다음 사용자를 대신해 NGINX API와 통신하는 스크립트만 있으면 되므로 아주 좋을 것입니다. 다만 NGINX가 재시작될 경우를 대비해 템플릿 시스템도 필요할 가능성이 높다는 점이 유일한 문제입니다. 그러면 바로 그 자리에서 업스트림 구성을 다시 로드할 수 있습니다.
다른 사람들이 NGINX Lua에서 Consul을 직접 쿼리하도록 구현한 사례도 있습니다. 하지만 아직 좋은 구현은 보지 못했습니다. 대체로 두 가지 방식 중 하나이기 때문입니다. 첫 번째는 HTTP 요청이 들어올 때마다 Consul을 쿼리하는 방식인데, 속도가 느립니다. 두 번째는 구성을 로드할 때 데이터를 불러오는 방식인데, 그러면 DNS에서 겪는 것과 같은 문제가 발생합니다. 따라서 현재로서는 템플릿 방식이 가장 좋지만, 앞으로 NGINX Lua로 이를 구현할 방법이 나와도 놀랍지는 않을 것 같습니다.
질문: Chef를 더 이상 사용하지 않게 될 가능성도 있나요?
A: Consul에는 이러한 템플릿을 관리할 만한 훌륭한 기본 기능이 없고, 저희는 이미 Chef를 사용해 NGINX를 배포하고 있습니다. 로깅, 추적, 일부 OAuth 등 NGINX로 수행하는 다른 모든 작업을 위한 관련 레시피는 현재 수백 줄, 어쩌면 수천 줄에 달합니다. 저희 인프라에서 Chef를 없애고 다른 것으로 대체할 생각은 없지만, Consul은 Chef를 훌륭하게 보완하며 신속하게 알림을 받는 데 유용합니다.
Q: Chef로 인해 발생하는 오버헤드는 어느 정도인가요?
A: 대부분의 머신이 단일 작업만 수행하기 때문에 실제 Chef 오버헤드는 상당히 낮습니다. 유휴 상태일 때는 RAM 사용량도 적습니다. 실제로는 타이머에서 대기하다가 별도의 프로세스를 생성할 수 있습니다. 수렴 속도가 중요하지 않은 경우에는 cron으로 실행하는 사람들도 있습니다. 일부 부하가 큰 레시피에서는 CPU 사용량이 약간 증가하지만, 서버를 하나 더 배포하면 해결할 수 있는 수준입니다.
Q: CloudFormation을 얼마나 사용해 보셨나요?
A: 아직 CloudFormation을 사용하고 있지는 않습니다. 도입을 고려하고 있지만, 한 가지 달라진 점은 Auto Scaling 그룹을 훨씬 더 많이 활용하게 되었다는 것입니다. 인스턴스가 시작되면 해당 인스턴스의 Chef 실행 목록이 포함된 사용자 데이터를 갖게 되는 방식으로 작동합니다.
새 머신은 이미지에 기반한 키가 이미 포함된 상태로 클러스터에 추가되므로, Chef 서버와 통신하고 자체 등록한 뒤 필요한 쿡북을 내려받을 수 있습니다. 저희는 CloudFormation과 함께 사용할 만한 다른 AWS 도구를 많이 사용하지 않기 때문에, 이 방식으로 CloudFormation과 관련된 요구 사항 대부분을 해결할 수 있습니다.
질문: 서버가 폐기되는 상황에는 어떻게 대처하시나요?
A: Auto Scaling 그룹에서 서버가 사라지면 Amazon SNS[Simple Notification Service]를 사용해 알림을 받을 수 있습니다. 저희는 이러한 알림을 수신한 다음 Chef 서버를 정리하는 매우 간단한 서비스를 운영하고 있습니다.
Q: AWS의 OpsWorks를 사용해 보셨나요?
A: 사용해 본 적은 없습니다. 처음부터 호스팅형 Chef를 사용해 왔는데, 저희에게는 아주 잘 맞았습니다. 확실하지는 않지만, OpsWorks는 프로비저닝 용도로만 사용하는 Chef Solo에 더 가깝다고 생각합니다. 저희는 여전히 거의 모든 머신에서 Chef Agent를 실행하고 있으며, 동일한 Chef 레시피를 사용해 개발용 Docker 컨테이너를 생성합니다.
36:31 연락처 정보
관련 블로그 게시물
시청하기: KubeCon North America 2023에서의 NGINX Gateway Fabric
이 영상에서는 KubeCon의 NGINX 부스를 찾아가, 저희가 공개한 Kubernetes Gateway API 구현을 살펴봅니다.
F5 NGINX로 대규모 WAF 플릿을 확인하고 관리하는 2가지 방법
앱 보안을 강화하는 두 가지 방법을 소개합니다. NGINX Management Suite를 통해 WAF 운영에 대한 가시성을 높이고, NGINX App Protect WAF를 활용해 코드형 보안을 구현하는 것입니다.
Ingress 컨트롤러 선택 가이드, 4부: NGINX Ingress Controller 옵션
개발 주체, 개발 철학, 프로덕션 환경 준비도, 보안 및 지원을 기준으로 어떤 NGINX Ingress 컨트롤러가 가장 적합한지 알아보세요.
진화하는 공격 환경에 NGINX App Protect Denial of Service가 적응하는 방법
NGINX App Protect Denial of Service(DoS)가 계층 7 DoS 공격을 방어하기 위해 완화 방법을 실시간으로 조정하는 방식을 설명합니다. 정상적인 사용자 행동과 서버 상태에 대한 모델을 생성하여 공격을 나타내는 이상 징후를 감지하고 맞춤형 완화 시그니처를 적용할 수 있습니다.
NGINX Instance Manager를 활용한 대규모 구성 업데이트 및 인증서 관리
NGINX Instance Manager 버전 1.0에는 두 가지 새로운 기능이 도입되었습니다. NGINX 인스턴스에 태그와 사용자 역할을 지정하면 전체 그룹에 구성을 한 번에 적용할 수 있습니다. 인증서 검사는 만료 예정인 인증서를 감지하여 서비스 중단을 방지할 수 있도록 인증서를 교체할 수 있게 해줍니다.
Netflix가 자사 CDN의 핵심으로 NGINX를 선택한 이유
Netflix가 글로벌 콘텐츠 전송 네트워크인 Open Connect의 스트리밍 미디어 기술로 NGINX를 선택한 이유를 NGINX 개발자 Gleb Smirnoff에게서 알아보세요.