Recommanded Free YOUTUBE Lecture: <% selectedImage[1] %>

Contents

Service Discovery

컨테이너 기반의 PaaS에서는 다양한 서비스들이 네트워크로 연결된다. 이들 서비스를 연결해서 완성된 애플리케이션으로 만들기 위해서는 각 서비스들이 서로를 discovery 할 수 있어야 한다. 가장 쉬운 방법은 서비스에 도메인 이름을 붙여주는 것이다.

예를 들어 워드프레스 애플리케이션을 실행한다고 가정해 보자. 워드프레스를 실행하기 위해서는 PHP+Apache와 Mysql이 필요할 것이다. 아마도 PHP+Apache로 구성된 컨테이너와 Mysql을 서비스하는 컨테이너로 나뉠테다. PHP+Apache 컨테이너는 도메인이름으로 Mysql을 찾을 수 있어야 한다. 대략 아래와 같은 그림이 될거다.

 DNS를 이용한 Service discovery

PaaS는 IaaSSaaS 즉, 클라우드 기반의 시스템이다. 컨테이너들은 클라우드를 구성하는 인스턴스들을 떠다닌다. IP가 고정되지 않으므로 다이나믹한 DNS 환경의 구성이 필요하다. Mysql 컨테이너가 만들어지면, 컨테이너가 만들어진 IP 주소를 가리키는 DNS 이름 mysql-001.joinc.priv가 만들어진다. 이제 PHP+Apache 컨테이너는 mysql-001.joinc.priv를 이용해서 Mysql 컨테이너가 적재된 인스턴스를 찾게 된다. 인스턴스에는 MysqlProxy서버가 있어서, 목적지 Mysql 컨테이너로 Proxy 해준다.

이 구성은 네트워크에 따라 달라질 수 있다. L2 Flat 네트워크를 구성한다면(즉 모든 컨테이너에 IP를 할당한다면) MysqlProxy 필요 없이 Mysql 컨테이너로 직접 연결 할 수 있을 것이다.

PowerDNS

다이나믹 DNS를 구성하기 위한 네임서버를 찾아야 한다. 가장 널리 사용하고 있는 네임서버는 bind9이다. nsupdate를 이용해서 다이나믹하게 네임 정보를 업데이트 할 수 있기는 하지만, 데이터가 로컬 데이터베이스에 저장되기 때문에 가용성확장성을 확보하기가 어렵다. 그래서 Mysql같은 RDBMS를 백앤드로 사용 할 수 있는 PowerDNS로 네임서비스를 만들기로 했다. PowerDNS의 특징은 아래와 같다.
  • mysql, postgresql, sqlite3등 데이터베이스를 백앤드로 사용 할 수 있다.
  • Dynamic DNS를 구축하기 쉽다. SaaS나 PaaS를 위한 service discovery 용도로 사용하기 위해서는 중앙에 데이터를 저장해야 할 필요가 있다. 데이터베이스 지원이 중요하다.

설치

환경

VirtualBox를 이용해서 아래와 같은 테스트 환경을 구성했다.

 PDNS 테스트 환경
  • pdns server : PowerDNS 서버와 Mysql 서버를 설치한다.
  • 우분투 리눅스 15.10 버전을 사용한다.
  • Container instance : PaaS Instance 역할을 한다. 여기에 컨테이너가 실행되고, DNS 기반으로 컨테이너에 연결한다.

Mysql 설치 및 설정

먼저 mysql-server를 설치한다.
# apt-get install mysql-server

데이터베이스와 테이블을 만들자. 설치 스크립트가 없어서 직접 만들어야 한다.
CREATE DATABASE powerdns;
CREATE TABLE domains (
  id                    INT AUTO_INCREMENT,
  name                  VARCHAR(255) NOT NULL,
  master                VARCHAR(128) DEFAULT NULL,
  last_check            INT DEFAULT NULL,
  type                  VARCHAR(6) NOT NULL,
  notified_serial       INT DEFAULT NULL,
  account               VARCHAR(40) DEFAULT NULL,
  PRIMARY KEY (id)
) Engine=InnoDB;

CREATE UNIQUE INDEX name_index ON domains(name);

CREATE TABLE records (
  id                    INT AUTO_INCREMENT,
  domain_id             INT DEFAULT NULL,
  name                  VARCHAR(255) DEFAULT NULL,
  type                  VARCHAR(10) DEFAULT NULL,
  content               VARCHAR(64000) DEFAULT NULL,
  ttl                   INT DEFAULT NULL,
  prio                  INT DEFAULT NULL,
  change_date           INT DEFAULT NULL,
  disabled              TINYINT(1) DEFAULT 0,
  ordername             VARCHAR(255) BINARY DEFAULT NULL,
  auth                  TINYINT(1) DEFAULT 1,
  PRIMARY KEY (id)
) Engine=InnoDB;

CREATE INDEX nametype_index ON records(name,type);
CREATE INDEX domain_id ON records(domain_id);
CREATE INDEX recordorder ON records (domain_id, ordername);


CREATE TABLE supermasters (
  ip                    VARCHAR(64) NOT NULL,
  nameserver            VARCHAR(255) NOT NULL,
  account               VARCHAR(40) NOT NULL,
  PRIMARY KEY (ip, nameserver)
) Engine=InnoDB;


CREATE TABLE comments (
  id                    INT AUTO_INCREMENT,
  domain_id             INT NOT NULL,
  name                  VARCHAR(255) NOT NULL,
  type                  VARCHAR(10) NOT NULL,
  modified_at           INT NOT NULL,
  account               VARCHAR(40) NOT NULL,
  comment               VARCHAR(64000) NOT NULL,
  PRIMARY KEY (id)
) Engine=InnoDB;

CREATE INDEX comments_domain_id_idx ON comments (domain_id);
CREATE INDEX comments_name_type_idx ON comments (name, type);
CREATE INDEX comments_order_idx ON comments (domain_id, modified_at);


CREATE TABLE domainmetadata (
  id                    INT AUTO_INCREMENT,
  domain_id             INT NOT NULL,
  kind                  VARCHAR(32),
  content               TEXT,
  PRIMARY KEY (id)
) Engine=InnoDB;

CREATE INDEX domainmetadata_idx ON domainmetadata (domain_id, kind);


CREATE TABLE cryptokeys (
  id                    INT AUTO_INCREMENT,
  domain_id             INT NOT NULL,
  flags                 INT NOT NULL,
  active                BOOL,
  content               TEXT,
  PRIMARY KEY(id)
) Engine=InnoDB;

CREATE INDEX domainidindex ON cryptokeys(domain_id);


CREATE TABLE tsigkeys (
  id                    INT AUTO_INCREMENT,
  name                  VARCHAR(255),
  algorithm             VARCHAR(50),
  secret                VARCHAR(255),
  PRIMARY KEY (id)
) Engine=InnoDB;

CREATE UNIQUE INDEX namealgoindex ON tsigkeys(name, algorithm);

PowerDNS - mysql 패키지 설치

PowerDNS 패키지를 설치한다.
# apt-get install pdns-backend-mysql
중간에 데이터베이스 설정을 묻는 대 창이 뜬다. No로 넘어가자.

PowerDNS 설정

pdns 설정파일은 /etc/powerdns/pdns.d 에 있다. pdns 백엔드 타입별 샘플 설정파일들이 있는데, pdns.local.gmysql.conf만 남기고 모두 삭제한다. 아래와 같이 pdns.local.gmysql.conf 파일을 수정했다.
launch=gmysql
gmysql-host=127.0.0.1
gmysql-port=3306
gmysql-dbname=powerdns
gmysql-user=root
gmysql-password=rootpassword

pdns service를 재 시작한다.
# service pdns restart

Poweradmin 설치

pdns를 관리하기 위해서 데이터베이스를 직접 조작하려면 상당한 노가다가 필요하다. 웹 기반의 관리 툴인 Poweradmin을 이용해서 관리하기로 했다. Apache와 PHP5를 설치한다.
# apt-get install -y apache2 gettext libapache2-mod-php5 php5 php5-common php5-curl \
php5-dev php5-gd php-pear php5-imap  php5-ming php5-mysql php5-xmlrpc php5-mhash php5-mcrypt

여기에서 poweradmin을 다운로드 한다. (2016년 6월 8일)현재 최신 버전은 poweradmin-2.1.7.tgz 이다. 압축을 풀어서 /vaw/www/html 로 복사한다.
# tar -xvzf poweradmin-2.1.7.tgz
# mv poweradmin-2.1.7 /var/www/html/poweradmin
# chown -R www-data.www-data /var/www/html/poweradmin 
설정 파일을 만들고 권한을 설정한다. 설정파일 내용은 install과정을 끝내고 나면 채워진다.
# touch /var/www/html/poweradmin/inc/config.inc.php
# chown www-data.www-data /var/www/html/poweradmin/inc/config.inc.php 

브라우저를 이용해서 인스톨 페이지 /poweradmin/install에 접근한다.

 step-1

언어를 선택한다.

 step-3

데이터베이스 설정을 한다.
  • Username : 데이터베이스 접근에 사용할 유저 이름
  • Password : Username이 사용 할 패스워드
  • Database type : Mysql
  • Hostname : 로컬호스트에서 테스트했다.
  • DB Port : 기본 포트 3306
  • Database : Pdns가 사용할 데이터베이스 이름
  • Poweradmin administrator password : 웹으로 로그인 할 때 사용할 어드민(admin) 패스워드
 step-4

기타 설정
  • Username : poweradmin 웹 에서 사용할 새로운 유저를 추가한다.
  • Password : poweradmin 유저가 사용할 패스워드.
  • Hostmaster :
  • Primary nameservero & Secondary nameserver
 step-5

앞서 설정한 poweradmin 유저 접근 할 수 있도록 GRANT설정을 한다. 웹 페이지에 있는 쿼리를 그대로 실행하면 된다.

 step-6

지금까지의 내용으로 config.inc.php만들어졌다.

 step-7

설정 끝

poweradmin 사용

http://192.168.56.101/poweradmin 으로 접근해서 로그인 한다.

 login

이제 도메인을 추가해 보자. Add master zone을 클릭해서 example.com을 위한 Zone을 추가한다.

 Add master zone

List zones을 클릭하면 현재 관리중인 zone 목록을 확인 할 수 있다. 편집 아이콘을 클릭하면 레코드를 추가할 수 있다.

 List zones

www.example.com을 추가해 보자.

 A record 추가

dig를 이용해서 제대로 등록이 됐는지 테스트를 했다.
$ dig www.example.com A @192.168.56.101

; <<>> DiG 9.9.5-11ubuntu1-Ubuntu <<>> www.example.com A @192.168.56.101
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 43114
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1680
;; QUESTION SECTION:
;www.example.com.		IN	A

;; ANSWER SECTION:
www.example.com.	86400	IN	A	192.168.56.5

;; Query time: 3 msec
;; SERVER: 192.168.56.101#53(192.168.56.101)
;; WHEN: Sat Jun 11 02:06:35 KST 2016
;; MSG SIZE  rcvd: 60
www.example.com이 등록된 걸 확인 할 수 있다.

Service Discovery 시스템 구축

pdns를 살펴본 이유는 컨테이너 기반의 SaaS 인프라에서 Service Discovery 시스템을 구축하기 위함이었다. VM(가상머신) 기반의 IaaS 환경에서 웹 서비스를 만들었다고 가정해 보자. 이 경우 웹 서비스는 VM위에 올라가므로 IP를 찾는 것 만으로 웹 서비스에 즉시 연결 할 수 있다. 아래와 같은 구성일테다.

 VM 기반에서의 서비스 디스커버리

App를 위해서 www.example.com 도메인을 할당한다. 이때 Private DNS에 App이 실행 중인 VM의 IP만 등록하면 된다. 인터넷에서의 유저 요청은 Public DNS를 거쳐서 로드밸런서에 도착을 하면, 로드밸런서는 Private DNS를 resolver로 해서 App이 실행 중인 vm을 찾는다. 어렵지 않게 구성 할 수 있다.

하지만 컨테이너 기반이라면 문제가 복잡해 진다. VM기반의 경우에는 VM의 IP만 찾는 것으로 끝이지만 컨테이너의 기반일 경우 VM에서 실행된 컨테이너까지 찾아야 하기 때문이다. 이 문제를 해결 하기 위한 좋은 방법과 그다지 좋지 않은 방법이 있다. 가능한 방법을 모두 살펴보자.

먼저 별로 좋지 않은 방법이다.

 service discovery ver 1

유저 (컨테이너)App을 찾기 위해서, Public DNS, Private DNS, Proxy Database 총 3개의 데이터베이스가 필요하다. 컨테이너 서비스를 만들 경우 아래의 작업을 통해서 컨테이너를 찾을 수 있도록 한다.
  1. Private DNS : 컨테이너 애플리케이션이 실행 중인 VM을 찾기 위해서 사용한다.
  2. Proxy Database : 컨테이너의 IP와 Port 정보를 가지고 있다. Proxy server는 이 데이터베이스를 이용해서 app을 찾는다.
좋지 않은 방법이라고 했지만 그렇다고 써먹지 못할 그런 방법은 아니다. 충분히 잘 작동하는 방법이다. 다만 컨테이너를 찾기 위한 동일한 목적을 가진 두 개의 데이터베이스가 필요하다는 문제점이 있다. 동일한 목적을 가진 정보는 하나의 데이터베이스에 담는게 기본적인 설계 원칙이다. 이 방식의 경우 Private DNS와 Proxy Database의 데이터를 서로 동기화 시켜줘야 하는데, 애플리케이션의 정상적이지 않은 작동이나 종료들을 고려해야 하기 때문에 실제 구현은 상당히 복잡해질 수 있다.

그래서 Proxy DatabasePrivate DNS를 통합하기로 했다. 아래의 구성을 보자.

 srv를 이용한 service discovery

DNS 서버는 SRV 레코드를 가지고 있다. NginX나 HaProxy와 같은 애플리케이션들은 SRV레코드를 resolve 할 수 있는 기능을 가지고 있다. SRV resolve를 이용하면 도메인이름으로 부터 IP와 Port까지 가져올 수 있다. 이렇게 구성을 하면 별도의 Proxy Database를 만들 필요 없이 DNS 만으로 service discovery 시스템을 구축 할 수 있다.

PDNS의 SRV Record 테스트

PDNS가 SRV Record를 제대로 지원하는지 테스트 해보기로 했다.

 PDNS 구축

nslookup으로 테스트한 결과다.
# nslookup -query=srv _http._tcp.example.com 127.0.0.1 
Server:		127.0.0.1
Address:	127.0.0.1#53

_http._tcp.example.com	service = 10 10 8080 smallbox2.example.com.
_http._tcp.example.com	service = 10 10 8086 smallbox2.example.com.
_http._tcp.example.com	service = 10 20 8080 smallbox1.example.com.
_http._tcp.example.com	service = 10 60 8080 bigbox.example.com.
_http._tcp.example.com	service = 20 0 8080 backupbox.example.com.
TCP 기반의 HTTP 서비스에 대한 호스트이름과 포트번호 목록을 얻을 수 있다. 클라이언트 프로그램은 이들 목록 중에서 원하는 서비스의 호스트를 선택해서 연결을 하면 된다. 물론 호스트이름은 A 혹은 AAAA 레코드로 등록돼 있어야 할 것이다. 즉 서비스 애플리케이션을 찾기 위해서는 1. SRV Resolve 2. Name Record Resolve, 총 두 번의 DNS 질의를 해야 한다.

traefik 를 이용해서 구현해 보기로 했다.

Service discovery 환경을 만들기 위해서는 SRV resolve 기능을 가진 프락시 서버가 필요하다. HAProxy, NginX(모듈 형태로 지원한다.) 등의 웹 서버들이 SRV를 지원한다. 나는 최근 뜨고? 있는 traefik를 이용해서 만들어 보기로 했다.

소개

아래 그림 한장으로 traefik 설계 목적을 대략 이해할 수 있다.

 araefik 아키텍처

컨테이너가 일반화 되고, MSA와 REST API가 널리 사용되면서 포트 기반의 리버스 프락시 서버가 필요해지고 있다. 이런 추세에 맞춰서 HAProxy와 NginX등의 주요 웹 서버들이 SRV Resolve 기능을 지원하고 있기는 하다. 하지만 추세를 따르기 위해서 기능만 우겨 넣었다는 느낌이 강하다. traefik 는 아예 컨테이너와 MSA & REST를 지원하기 위해서 만들어진 HTTP 기반의 프락시 서버다.

go언어로 만들었기 때문에 의존성 걱정 없이 간단하게 설치해서 사용 할 수 있다는 장점이 있다. 다른 프락시 서버처럼 이것 저것 라이브러리 설치하고 언어 설치하고 그럴 필요가 없다. 그냥 실행 파일 하나로 끝이다. 최신 추세를 반영하고 있는 만큼 HTTP/2, websocket 등도 지원 하고 있다.

설치와 실행

실행 파일 하나만 배포하기 때문에 설치도 간단하다. traefik releases 사이트에 들어가서 시스템에 맞는 바이너리 파일 다운로드 하면 끝이다. 나는 traefik_linux-amd64를 다운로드 해서 /usr/bin 에 복사했다.
# wget https://github.com/containous/traefik/releases/download/v1.0.2/traefik_linux-amd64
# chmod +x traefik_linux-amd64 
# mv traefik_linux-amd64 /usr/bin/traefik