iOrchard http://www.iorchard.net/ Virtualization, Cloud Computing Expert Group en-us Wed, 12 Jul 2017 00:00:00 +0900 http://www.iorchard.net/2017/07/12/안양_porch_ucim_minion_app_배포_문제_기술지원.html http://www.iorchard.net/2017/07/12/안양_porch_ucim_minion_app_배포_문제_기술지원.html <![CDATA[안양 PORCH ucim-minion App 배포 문제 기술지원]]> 안양 PORCH ucim-minion App 배포 문제 기술지원

안양 NW인증팀에서 ares-k33이라는 서버에 CentOS 7 자동 설치하고 ucim-minion App을 배포하는데 실패했다고 한다.

로그 파일을 보니 docker 설치에서 에러가 났다.

/path/to/logfile:

TASK [ucim-minion : Install docker container engine package] *******************
failed: [106.241.8.33] (item=['docker', 'python-pip']) =>
{"changed": false, "failed": true, "item": ["docker", "python-pip"],
"msg": "\n\n One of the configured repositories failed (Unknown),\n
and yum doesn't have enough cached data to continue.
At this point the only\n safe thing yum can do is fail. There are a few
ways to work \"fix\" this:\n\n

yum이 패키지를 가져오지 못하고 있다.

왜?

문제점

기본적으로 안양 환경은 배포대상서버는 나가는 패킷도 서비스 port를 제외하고는 차단한다. 예를 들면 DNS서버는 tcp/udp port 53으로부터 나가는 패킷만 허용하는 것이다. 방화벽에서 하든, ACL 관리 서버에서 하든 아무튼 위와 같이 제한한다.

따라서 배포대상서버가 외부 인터넷으로부터 무엇인가를 가져오려면 PORCH를 통해야 한다. 안양 PORCH 시스템은 WAN 구간 지원을 하기 위해 PORCH의 외부 IP로 proxy 설정을 해 놓았다.

CentOS 7 자동 설치 과정에서 yum.conf에 PORCH의 외부 IP로 proxy 설정을 한다.

/etc/yum.conf:

...
proxy=http://<porch_ip>:<porch_proxy_port>

보안 상 IP와 port는 적지 않는다. (기본 proxy 포트는 TCP 3128 이다.)

위 문제는 PORCH proxy 서비스에 접속이 되지 않아 패키지를 설치할 수 없었던 것이다.

해결책

배포대상서버 IP에서 PORCH의 IP와 proxy port로의 접속을 허용해 주어야 한다.

Epilog

위 해결책보다 더 좋은 방법은 내/외부 배포대상서버를 구별하여 자동으로 proxy 접속을 하는 것이다.

PORCH proxy 서비스는 배포망/서비스망 모두 서비스하고 있다.

배포대상서버가 WAN 건너편에 있는 서버라면, 위와 같이 서비스망 proxy 접속 허용은 여전히 필요하다.

배포대상서버가 배포망에 있는 서버라면 우리는 배포망은 trusted network으로 간주하니 배포망 proxy로 접속하면 되기 때문에 따로 접속 허용을 할 필요는 없다.

그러나 현재 PORCH는 그렇게 똑똑하지 않다.

]]>
Wed, 12 Jul 2017 00:00:00 +0900
http://www.iorchard.net/2017/07/03/bacula.html http://www.iorchard.net/2017/07/03/bacula.html <![CDATA[Bacula 설치부터 운영까지]]> Bacula 설치부터 운영까지

개요

Bacula 는 C++ 프로그래밍 언어로 개발되고 GNU(General Public License) 버전 2.0 오픈 소스 네트워크 기반 백업 프로그램이다. ᅟBacula 라는 이름은 Backup 과 Dracula 에 혼합어이다. 드라큘라처럼 데이터를 흡혈하듯 백업한다는 재미난 이름이다.

Bacula 의 특징으로는 다음과 같다.

  1. 쉬운 설치 및 설정
  2. 다양한 백업 장치 지원 (하드디스크, ᅟLTO 1-5 테이프 장치 등등)
  3. 다양한 모니터링툴 지원
  4. 증분, 차등, 차등 증분, 변경분, 가상 백업, 백업 주기, 압축 프로토콜 등 다양한 백업 방식 지원
  5. 백업 전후 실시할 명령어 지정이 가능
  6. 네트워크로 백업되는 데이터의 암호화를 위한 다양한 옵션 (MD5, SHA, TLS, CRAM-MD5 등등) 지원
  7. 데이터 압축 전송
  8. 네트워크 bandwidth 제한 가능

Bacula 구조

Bacula 구성 요소

아래 구조를 정확히 이해해야 Bacula 백업 관리 및 운영이 가능하다. 하지만 처음 구조를 살펴 보면 이해가 잘 안가고 복잡해 보일 수 있다. Bacula 는 ᅟDirector, Console, File, Storage 및 Monitor 5가지 주요 서비스와 ᅟCatalog 라는 하나의 DB 로 구성되며 이 서비스들은 얼마든지 확장이 가능하다.

출처 : http://www.bacula.org/7.9.x-manuals/en/main/What_is_Bacula.html

ᅟBacula 는 Catalog 라고 백엔드 서비스가 존재하는데 백업에 관련된 모든 정보를 Catalog 라는 DB에 저장하여 관리한다. 공식적으로 mysql, postgresql, sqlite, sqlite3 를 지원하며 실무용으로 사용한다면 sqlite 는 권장하지 않는다. 이로 인하여 ᅟBacula 의 Catalog 에 대한 백업도 중요시 되는게 사실이다. 그래서 자체적으로 Catalog 를 백업하는 스크립트가 존재하며 DB가 손상되었을 경우 수동으로 복원이 가능한 CLI 툴을 이용하여 복원이 가능하다.

DB 를 제외한 서비스에 대하여 알아보자.

Bacula Director

bacula direcotr 서비스는 client 들과 통신하며 모든 백업, 복원, 확인 및 기록하는 작업을 감독하는 프로그램이다. default 포트는 9101 이다.

Bacula Console

bacula console 서비스는 ᅟbacula direcor 와 통신하여 명령을 수행할 수 있다. bacula console 에는 텍스트 기반 콘솔 인터페이스, qt 기반 인터페이스 및 wxWidgets 그래픽 인터페이스 세가지 버전이 있다. 대체적으로 많이 사용하는 인터페이스는 쉘에서 사용하는 텍스트 기반 콘솔 인터페이스이다.

Bacula File

bacula file 서비스(클라이언트 프로그램)는 백업 할 클라이언트에 설치해야 하는 프로그램이다. bacula director 서비스가 백업, 복원과 같은 작업을 요청시 파일 속성 정보와 데이터를 전달하는 역할을 한다. unix/linux 외에도 windows file 데몬이 있다. windows server 및 desktop 버전 에도 설치가 가능하다. default 포트는 9102 이다.

Bacula Storage

bacula storage 서비스는 파일 속성 및 데이터를 물리적인 백업 미디어 또는 볼륨에 저장하는 프로그램이다. 백업 미디어 또는 볼륨을 읽고 쓰는 역할을 한다. default 포트는 9103 이다.

Bacula Monitor

bacula monitor 서비스는 관리자 또는 사용자가 ᅟbacula 의 주요 데몬 상태를 볼 수 있게 해주는 프로그램이다. 현재 GNOME, KDE 또는 FreeDesktop.org 시스템 트레이 표준을 지원하는 모든 window manager 와 함께 작동하는 GTK + 버전만 사용할 수 있다.

지원 OS

Bacula는 BSD, Linux, Mac OS X, Unix 및 Windows와 같은 가장 널리 사용되는 플랫폼을 지원한다. client daemon의 경우 거의 모든 os 에 지원이 가능하다. 지속적으로 업그레이드가 이루어지고 있으며 아래 기재되어 있지 않은 window destkop 버전에서도 client daemon을 설치하여 백업이 가능하다.

bacula 홈페이지를 참조하였다. 출처 : https://blog.bacula.org/what-is-bacula/supported-operating-systems/

Bacula 설치(on Debian Stretch)

최근 debian stretch 가 release 되어 최신 버전에서 설치를 진행하였다. 현재 bacula 에 stable 최신 버전은 7.4 버전이다. bacula 설치 방법은 소스를 이용하여 컴파일하는 방법과 apt package install 유틸리티를 이용하여 설치하는 방법이 있다.

본 매뉴얼은 아래 가상서버의 구성으로 진행하였다. 최소 2대 이상의 서버를 준비하여 1대는 director, storage, monitor 서비스를 담당하고 1대는 백업 할 clientᅟ 서버로 준비하여 시험을 하기를 바란다.ᅟ windows 와 mac 은 부득이하게 제외하였다. 설치 할 client 프로그램과 방법만 다를 뿐 설정은 linux 와 동일하다.

서비스 Hostname OS IP
Bacula Direcotor bacula Debian Stretch 192.168.0.190
Bacula Storage bacula Debian Stretch 192.168.0.190
Bacula Console bacula Debian Stretch 192.168.0.190
Bacula Client bacula Debian Stretch 192.168.0.190
Bacula Client client1 Debian Stretch 192.168.0.191
Bacula Client client2 Debian Stretch 192.168.0.192

Bacula Server(Direcotr, Storage, Console)

위에서 소개한바와 같이 bacula 에는 역할이 다른 다양한 프로그램이 있고 백업을 위해서는 필수 프로그램이 구동되어야 한다. 클라이언트는 ᅟbacula file(bacula-fd) 프로그램만 구동이 되면 되지만 서버의 경우 bacula director(bacula-director), bacula storage(bacula-sd), bacula console(bconsole) 프로그램이 모두 정상적으로 구동되어야 백업 및 복원을 할 수 있다.

apt 를 이용하여 설치하는 방법으로 진행하였다. stretch 에서는 bacula 최신 버전(7.4)이 stable 버전으로 확인된다. bacula 에서는 다양한 db(mysql, postgresql, sqlite, sqlite3)를 지원한다. 본 매뉴얼에서는 postgresql 을 사용하였다.

# apt search bacula
# apt update
# apt install bacula-client bacula-common bacula-common-pgsql bacula-console bacula-director bacula-director-pgsql bacula-fd bacula-sd

ᅟbacula 를 사용하기 위해서는 db를 반드시 설치해야 하고 bacula db 를 설정해야만한다. 설치 과정 중 친절하게 ᅟ자동으로 설정을 해주겠다고 물어본다. yes 를 클릭하고 db가 설치 되 있는 host, 여기서는 localhost를 선택하고 db 패스워드를 입력한다.

설치가 끝나면 db 에 접속하여 설정이 잘 되었는지 확인해 본다. 아래와 같이 table 이 추가되었다면 정상적으로 db 설정이 잘 된 것이다.

# psql -h localhost -U bacula -d bacula
Password for user bacula:
psql (9.6.3)
SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, bits: 256, compression: off)
Type "help" for help.

bacula=> \dt
            List of relations
 Schema |      Name      | Type  | Owner
--------+----------------+-------+--------
 public | basefiles      | table | bacula
 public | cdimages       | table | bacula
 public | client         | table | bacula
 public | counters       | table | bacula
 public | device         | table | bacula
 public | file           | table | bacula
 public | filename       | table | bacula
 public | fileset        | table | bacula
 public | job            | table | bacula
 public | jobhisto       | table | bacula
 public | jobmedia       | table | bacula
 public | location       | table | bacula
 public | locationlog    | table | bacula
 public | log            | table | bacula
 public | media          | table | bacula
 public | mediatype      | table | bacula
 public | path           | table | bacula
 public | pathhierarchy  | table | bacula
 public | pathvisibility | table | bacula
 public | pool           | table | bacula
 public | restoreobject  | table | bacula
 public | snapshot       | table | bacula
 public | status         | table | bacula
 public | storage        | table | bacula
 public | unsavedfiles   | table | bacula
 public | version        | table | bacula
(26 rows)

bacula=>

ᅟBacula Client

백업 대상 클라이언트에는 bacula file(bacula-fd) 프로그램만 구동이 되면 된다. 마찬가지로 apt 를 이용하여 설치한다.

# apt update
# apt install bacula-fd

Bacula 설정

Settings Bacula Director

ᅟBacula Client 설정 방법은 매우 간단하기에 Director -> Storage -> Client 순서로 설정을 진행해 본다. 위에서 설명한바와 같이 Director 서비스는 감독관 같은 개념으로서 백업에 관한 명령을 수행하는 서비스로 가장 중요한 서비스이다.

각 섹션에 대하여 살펴보자.

Director {}

Direcotr 데몬의 기본적인 정보를 설정하는 부분이다. Director 서버 이름과 DirAddress 정보를 입력한다. Direcotr 서버 이름은 Client 설정에서 꼭 필요한 요소이며 동일한 이름이 설정되어야 한다. DirAddress 는 FQDN 또는 ip 주소를 입력한다. 추가로 Maxium Concurrent Jobs 항목이 있는데 최대 동시 작업 수 를 지정하는 항목이다. 한번에 한 서버만 백업이 실행되길 원하거나 서버의 성능에 맞춰 조정하고 싶다면 해당 항목의 숫자를 변경하면 된다.

Director {                            # define myself
  Name = bacula-dir
  DIRport = 9101                # where we listen for UA connections
  QueryFile = "/etc/bacula/scripts/query.sql"
  WorkingDirectory = "/var/lib/bacula"
  PidDirectory = "/run/bacula"
  Maximum Concurrent Jobs = 20
 - Password = "Pzou7xJvjGunHtRkPPtGQw1vQkG_XfTNG"         # Console password
 + Password = “vmffpdlsxprtmxm1!”
  Messages = Daemon
  DirAddress = 127.0.0.1
}

Catalog {}

위에서 설명한 바와 같이 ᅟCatalog 라고 하는 DB 를 사용한다고 하였다. 위 설치 과정으로 설치하였다면 bacula-dir.conf 설정 ᅟ파일에 자동으로 정보가 입력되었을 것이다. 정보가 변경이 되었다면 catalog 섹션에서 정보를 수정하면 된다.

Catalog {
  Name = MyCatalog
  dbname = "bacula"; DB Address = "localhost"; dbuser = "bacula"; dbpassword = "vmffpdlsxprtmxm1!"
}

Messages {}

백업의 성공 혹은 에러 등 job 을 수행한 뒤에 메시지들을 이메일로 보내주는 기능이 있다. Messages 섹션은 Standard, Daemon 2가지 종류로 나뉘는데 Standard 는 job 의 백업 및 복원을 수행시 결과에 대한 메시지를 담당하며 ᅟDaemon 은 Dir, Storage, Client 간의 통신 상태에 대한 메시지를 담당한다. 별도 메일 구성없이 mailcommand 항목에 smtp 서버의 호스트명을 입력하고 mail 항목에 발신자 주소만 수정하면 메시지 발송이 가능하다.

Messages {
  Name = Standard

  - mailcommand = "/usr/sbin/bsmtp -h localhost -f \"\(Bacula\) \<%r\>\" -s \"Bacula: %t %e of %c %l\" %r"
  + mailcommand = "/usr/sbin/bsmtp -h iorchard.co.kr -f \"\(Bacula\) \<%r\>\" -s \"Bacula: %t %e of %c %l\" %r"
  - operatorcommand = "/usr/sbin/bsmtp -h localhost -f \"\(Bacula\) \<%r\>\" -s \"Bacula: Intervention needed for %j\" %r"
  + operatorcommand = "/usr/sbin/bsmtp -h ᅟiorchard.co.kr -f \"\(Bacula\) \<%r\>\" -s \"Bacula: Intervention needed for %j\" %r"
  - mail = root = all, !skipped
  + mail = sungmin@iorchard.co.kr = all, !skipped
  - operator = backup@iorchard.co.kr = mount
  + operator = root = mount
  console = all, !skipped, !saved
  append = "/var/log/bacula/bacula.log" = all, !skipped
  catalog = all
}

Messages {
  Name = Daemon
  - mailcommand = "/usr/sbin/bsmtp -h localhost -f \"\(Bacula\) \<%r\>\" -s \"Bacula daemon message\" %r"
  + mailcommand = "/usr/sbin/bsmtp -h iorchard.co.kr -f \"\(Bacula\) \<%r\>\" -s \"Bacula daemon message\" %r"
  - mail = root = all, !skipped
  + mail = sungmin@iorchard.co.kr = all, !skipped
  console = all, !skipped, !saved
  append = "/var/log/bacula/bacula.log" = all, !skipped
}

Storage {}

여기서부터는 ᅟ다양한 옵션을 제공한다. 한가지 핵심은 Storage 섹션은 저장장치를 지정하는 역할을 한다. 아래 Storage 설정을 진행하겠지만 백업 저장 장치를 Stroage 서비스에서 설정하게 되고 해당 Device를 지정하는 Director 에서 Storage 섹션에서 설정하게 된다. Storage 설정과 매치되어야 하는 항목들은 아래와 같다.

Storage {
  - Name = File1
  + Name = Client1
# Do not use "localhost" here
  - Address = localhost                # N.B. Use a fully qualified name here
  + Address = 192.168.0.190                # N.B. Use a fully qualified name here
  SDPort = 9103
  - Password = "MsENe5HdfIbZGSlrCGIJXvhS52-nlo8Hz"
  + Password = "vmffpdlsxprtmxm1!"
  - Device = FileChgr1
  + Device = Client1
  Media Type = File1
  Maximum Concurrent Jobs = 10        # run up to 10 jobs a the same time
}

Storage {
  - Name = File2
  + Name = Client2
# Do not use "localhost" here
  - Address = localhost                # N.B. Use a fully qualified name here
  + Address = 192.168.0.190                # N.B. Use a fully qualified name here
  SDPort = 9103
  Password = "MsENe5HdfIbZGSlrCGIJXvhS52-nlo8Hz"
  Password = "vmffpdlsxprtmx1!"
  - Device = FileChgr2
  + Device = Client2
  Media Type = File2
  Maximum Concurrent Jobs = 10        # run up to 10 jobs a the same time
}

Pool {}

Bacula 는 백업을 저장하는 파일을 tarball 처럼 하나의 파일로 묶고 이것을 테이프 장치처럼 백업을 하게 된다. 파일들은 보안상 내용을 볼 수 없도록 암호화하여 파일로 합쳐지게 되는데 이 파일을 Volume 이라고 부른다. Pool은 이 Volume 을 하나로 묶은 파일이다. 쉽게 생각하면 각각의 Volume이 테이프라고 생각하면 된다.

  • Name : Pool 의 이름을 지정한다.
  • ᅟPool Type : ᅟdefault 인 Backup 외에 Archive, Cloned Migration, Copy, Save 등 다양한 타입이 있다.
  • Recycle : 지정한 주기가 끝나면 처음으로 돌아가는 재활용 주기를 지정한다. 테이프로 생각하면 되감기로 생각하면 된다.
  • AutoPrune : 볼륨 내에 주기가 지난 파일들을 자동으로 삭제하는 옵션이다.
  • Volume Retension : 볼륨 자체의 주기를 지정한다. 365일(1년) 동안 사용하지 않는 볼륨이 있을시 볼륨을 삭제한다.
  • Maximum Volume Bytes : 볼륨의 최대 크기를 지정한다.
  • Maximum Voulmes : 볼륨의 최대 개수를 제한한다. 최대 볼륨 50G에 최대 볼륨 개수가 100 이라면 이 Pool 의 최대 사이즈는 50G x 100 = 5TB 이다.
  • Storage : Pool 을 저장할 Storage 를 지정한다.
  • Label Format : Storage 에 저장되는 Pool 의 이름을 형식에 맞게 자동으로 생성되도록 설정할 수 있다.
Pool {
  - Name = Default
  + Name = Client1
  Pool Type = Backup
  Recycle = yes                       # Bacula can automatically recycle Volumes
  AutoPrune = yes                     # Prune expired volumes
  Volume Retention = 365 days         # one year
  Maximum Volume Bytes = 50G          # Limit Volume size to something reasonable
  Maximum Volumes = 100               # Limit number of Volumes in Pool
  Storage = Client1
}

Pool {
  - Name = File
  + Name = Client2
  Pool Type = Backup
  Recycle = yes                       # Bacula can automatically recycle Volumes
  AutoPrune = yes                     # Prune expired volumes
  Volume Retention = 365 days         # one year
  Maximum Volume Bytes = 50G          # Limit Volume size to something reasonable
  Maximum Volumes = 100               # Limit number of Volumes in Pool
  Storage = Client2
  - Label Format = "Vol-"               # Auto label
  + Label Format = "Client2-"               # Auto label
}

Schedule {}

Backup 스케쥴을 설정한다. 설정이 직관적이기 때문에 간단히 살펴봐도 쉽게 이해할 수 있을 것이다. 여러가지 스케쥴 세트를 만들어 각 서버별로 다양한 스케쥴링 할 수 있다. 몇가지 스케쥴 설정을 보며 설명한다.

아래는 WeeklyCycle 이름의 스케쥴로 3가지 스케쥴이 설정되어 있다.

  • 매월 첫째주 일요일 23시 5분 전체 백업
  • 매월 둘째주부터 다섯째주까지 일요일 23시 5분 차등 백업
  • 매주 월요일부터 토요일 23시 5분 증분 백업
Schedule {
  Name = "WeeklyCycle"
  Run = Full 1st sun at 23:05
  Run = Differential 2nd-5th sun at 23:05
  Run = Incremental mon-sat at 23:05
}

아래는 Full Backup 이름의 스케쥴이다. 위와 같이 하나의 스케쥴에 모든 여러 스케쥴을 설정 할 수 있고 아래와 같이 하나씩 설정하여 목적에 맞는 스케쥴을 설정하여 지정해서 사용할 수 있다.

  • 매주 일욜부터 토요일(매일) 23시 10분 전체 백업

Schedule { Name = “Full Backup” Run = Full sun-sat at 23:10 }

FileSet {}

이 섹션은 백업할 client 의 백업 경로를 지정하는 섹션이다. 어떤 경로에 어떤 파일들을 백업할 것인가를 이 섹션에서 설정한다. 아래는 default 설정은 아니지만 설명을 위해 / 파티션을 백업 경로로 설정한 예제로 설명한다.

아래 링크를 참조하여 더 많은 옵션을 확인 할 수 있다. http://www.bacula.org/7.4.x-manuals/en/main/Configuring_Director.html#SECTION001770000000000000000

  • Name : File Set 의 이름을 지정한다.
  • Include : 백업 작업시 포함되어야 할 내용을 추가한다.
  • compression : 파일을 압축한다.
  • signature : md5 보다는 속도가 조금 느리지만 md5보다 4바이트 추ᅟ가되어 보안적인 면에서 좋다.
  • sparse : default 값은 no 이다. sparse file 을 백업할 경우 유용한 옵션이다.
  • file : 백업 경로를 설정한다.
  • exclude : 백업시 제외되어야할 경로를 설정한다.
fileSet {
  Name = "Full"
  Include {
    Options {
        compression=GZIP
        signature=SHA1
        Sparse=ᅟno
    }
    file = "/"
    }
    exclude {
    file = "/proc"
    file = "/sys/"
    file = "/selinux"
    file = "/media"
    file = "/mnt"
    file = "/dev"
    file = "/home/mysql"
    file = "/home/svn_back"
    }
}

JobDefs {} / Job {}

백업 및 복원 작업을 수행하는 핵심 섹션이다. JobDefs 는 공통적이거나 중복되는 설정을 하나로 정의하여 설정 할 수 있다. 위에서 설정한 각 섹션에서 필요한 섹션의 이름을 지정하여 설정한다.

JobDefs {
  Name = "DefaultJob"
  Type = Backup
  Level = Incremental
  - Client = bacula-fd
  + Client = client1-fd
  FileSet = "Full Set"
  Schedule = "WeeklyCycle"
  - Storage = File1
  + Storage = Client1
  Messages = Standard
  - Pool = File
  + Pool = Client1
  Write Bootstrap = "/var/lib/bacula/%c.bsr"
}

Client {}

백업할 client의 정보를 설정한다.

Client {
  - Name = bacula-fd
  + Name = client1-fd
  - Address = localhost
  + Address = 192.168.0.191
  FDPort = 9102
  Catalog = MyCatalog
  - Password = "HTj1bTUuybB7jkajzmMhOR78PwXp_UT3R"          # password for FileDaemon
  + Password = "vmffpdlsxprtmxm1!"          # password for FileDaemon
}

Settings Bacula Storage

설정이 까다로운 Director 설정이 끝났다면 Storage 설정을 살펴보자. Storage 설정 파일은 bacula-sd.conf 파일이다. 설치 후 해당 파일을 확인해보면 주석처리된 설정들이 많이 보일 것이다. 테이프 장치 및 여러가지 저장장치에 대한 옵션의 예제들이며 다양한 옵션들을 볼 수 있다. 해당 매뉴얼에서는 가상서버를 이용하여 시험하기에 하드디스크를 이용한 백업으로 설정하였다.

아래 그림을 보면 Direcotr 서비스보다 섹션이 적은 것을 확인 할 수 있다. Storage, Director, Messages 는 ᅟDirecotor 설정과 같이 서비스 서버의 설정 정보를 올바르게 입력하면 된다. Device 섹션에 대해서는 위 Storage 서비스를 소개한 바와 같이 데이터를 물리적인 백업 미디어 또는 볼륨에 저장하기 위하여 저장장치에 대한 설정을 한다.

아래는 아주 단순한 Device 설정 예제이다. Storage, Director 섹션에는 서비스에 맞는 정보를 입력하자. Director 서비스의 name 과 password 를 위 Director 설정에서 설정한 정보와 동일하게 입력해야 한다. Storage 섹션에 sdaddress 정보는 loopback 으로 설정하면 안되고 내부 네트워크나 FQDN 또는 ip 를 지정한다. Storage 섹션의 몇가지 옵션을 살펴보자.

  • Name : Device 의 이름을 지정한다.
  • Mediay Type : 미디어 타입은 2가지이다. File or Tape(LTO-1,LTO-2,LTO-3,LTO-4,LTO-5)
  • Archive Deivce : 백업되는 파일이 저장되는 경로를 지정한다.
  • LabelMedia : 테이프 장치에서 테이프의 label을 자동으로 인식한다. 하드디스크의 경우 자동으로 장치명을 label 한다.
  • Random Access : 하드디스크일 경우 yes, 테이프 장치라면 no 로 설정한다.
  • AutomaticMount : 백업 job 마다 저장되는 device 가 다를 경우, 자동으로 장치를 마운트 한다.
  • RemovableMedia : 제거 가능한 장치인 지정ᅟ한다. 하드디스크라면 no 로 설정한다.
  • AlwaysOpen : 하드디스크일 경우 no, 테이프 장치라면 yes 로 설정한다.ᅠ
Storage {                             # definition of myself
  Name = bacula-sd
  SDPort = 9103                  # Director's port
  WorkingDirectory = "/var/lib/bacula"
  Pid Directory = "/run/bacula"
  Maximum Concurrent Jobs = 20
  - SDAddress = localhost
  + SDAddress = 192.168.0.190
}

Director {
  Name = bacula-dir
  - Password = "Pzou7xJvjGunHtRkPPtGQw1vQkG_XfTNG"
  + Password = "vmffpdlsxprtmxm1!"
}

Device {
  Name = Client1
  Media Type = Client1
  Archive Device = /backup/Client1
  LabelMedia = yes;                   # lets Bacula label unlabeled media
  Random Access = Yes;
  AutomaticMount = yes;               # when device opened, read it
  RemovableMedia = no;
  AlwaysOpen = no;
  Maximum Concurrent Jobs = 5
}

Device {
  Name = Client2
  Media Type = Client2
  Archive Device = /backup/Client2
  LabelMedia = yes;                   # lets Bacula label unlabeled media
  Random Access = Yes;
  AutomaticMount = yes;               # when device opened, read it
  RemovableMedia = no;
  AlwaysOpen = no;
  Maximum Concurrent Jobs = 5
}

Messages {
  Name = Standard
  director = bacula-dir = all
}

설정이 완료되었다면 위 설정에서 ᅟ지정한 백업 저장 경로를 생성하고 소유권을 변경하고 Storage 서비스를 재시작하고 설정한 ip 정보로 포트가 해당 서비스로 시작되었는지 확인한다.

# ᅟmkdir -p /backup/Client1
# ᅟmkdir -p /backup/Client2
# chown bacula:bacula -R /backup
# systemctl restart bacula-sd
# ss -ltp | grep bacula-sd

Settings Bacula Client

마지막으로 ᅟClient 서비스의 설정을 진행하고 백업 및 복원을 시험해보자. Director 와 Storage 설정을 진행하였다면 Client 설정은 정말 쉽다. 간단히 설명하자면 Director 섹션은 위 Stroage 설정과 같이 Director 서비스의 설정과 동일하게 설정한다. 이 부분은 Director 서비스가 Client 서비스에 접속을 요청할 때 패스워드를 인증하는 부분이다.두번째 FileDaemon 섹션은 Client 서비스가 동작하는 설정이다. Client 서버의 FQDN 또는 ip 정보를 입력하자. 마지막 세번째 Messages 섹션은 작업 메시지를 처리하는 부분으로 메시지 작업을 수행하는 director 서비스를 지정한다.

Director {
  Name = bacula-dir
  - Password = "vmffpdlsxprtmxm1!"
  + Password = "vmffpdlsxprtmxm1!"
}

FileDaemon {                          # this is me
  Name = client1-fd
  FDport = 9102                  # where we listen for the director
  WorkingDirectory = /var/lib/bacula
  Pid Directory = /run/bacula
  Maximum Concurrent Jobs = 20
  Plugin Directory = /usr/lib/bacula
  - FDAddress = localhost
  + FDAddress = 192.168.0.191
}

# Send all messages except skipped files back to Director
Messages {
  Name = Standard
  director = bacula-dir = all, !skipped, !restored
}

참고로 패스워드에 대한 설명으로 ᅟᅟos 상에 ᅟbacula 패스워드는 별도 암호화를 지원하지 않는다. 각 서비스별 통신하는 과정에서는 암호화 되어져 통신이 이루어지지만 osᅟ 상에는 plain text 로 패스워드를 설정해야 한다. 또한 특별한 규칙은 정해져있지 않다.

Client 서비스 설정이 완료되었다면 서비스를 재시작하고 설정한 ip 정보로 포트가 해당 서비스로 시작되었는지 확인한다.

# systemctl restart bacula-fd
# ss -ltp | grep bacula-fd

Settings Bacula Console

bacula console 은 위에서 소개한 바와 같이 세가지 버전에 콘솔 인터페이스가 있다. cli 에서는 bconsole 이라는 텍스트 기반 콘솔을 사용한다. bconsole.conf 에 bacula director 프로그램 정보를 수정하여 콘솔에 접속 할 수 있다.

# vi /etc/bacula/bconsole.conf
...
Director {
  Name = bacula-dir
  DIRport = 9101
  address = localhost
 - Password = "wUlW_BhnSTqYuwN3tDblNprLpAcs73EMt"
 + Password = “vmffpdlsxprtmxm1!”
}

Bacula 운영

ᅟ위와 같이 bacula 를 설치하고 설정까지 완료가 되었다면 백업 및 복원 시험을 하여 문제가 없는지 점검한다. bacula console(bconsole)을 이용하여 수동 백업으로 시험해본다. 수동 백업은 Dirctor 서비스의 설정 파일(baculs-dir.conf)에 설정한 내용을 토대로 수동으로 백업을 진행한다. 자동 백업은 스케쥴 섹션에 설정 맞춰 진행이 될 것이다.

Bacula Console

백업 및 복원 시험을 진행하였다. ᅟbacula console 은 bacula director 와 연결하여 작업을 수행하게 된다. bacula 에서 제공하는 gui 툴이 있지만 cli 를 이용하여 시험한다. gui 에 대해서는 추가로 소개를 하겠다. bacula console은 bacula director 라는 감독에 지시로 동작하기에 관리자는 bacula director 설정을 모두 이해하고 있어야 한다.

위 설치 과정에서 bacula-console 패키지를 설치하였기에 bconsole.conf 설정 파일에 Director 서비스의 설정을 올바르게 수정하고 bconsole 을 실행하자.

Director {
  Name = bacula-dir
  DIRport = 9101
  address = localhost
  - Password = "wUlW_BhnSTqYuwN3tDblNprLpAcs73EMt"
  + Password = "vmffpdlsxprtmxm1!"
}

설정 파일에 문제가 없다면 Director 서버와 연결이 성공하게 되고 다양한 작업을 수행 할 수 있다. help 를 입력하여 수행 가능한 command 를 살펴보자.

# bconsole
Connecting to Director localhost:9101
1000 OK: 102 bacula-dir Version: 7.4.4 (20 September 2016)
Enter a period to cancel a command.
*
*help
  Command       Description
  =======       ===========
  add           Add media to a pool
  autodisplay   Autodisplay console messages
  automount     Automount after label
  cancel        Cancel a job
  create        Create DB Pool from resource
  delete        Delete volume, pool or job
  disable       Disable a job, attributes batch process
  enable        Enable a job, attributes batch process
  estimate      Performs FileSet estimate, listing gives full listing
  exit          Terminate Bconsole session
  gui           Non-interactive gui mode
  help          Print help on specific command
  label         Label a tape
  list          List objects from catalog
  llist         Full or long list like list command
  messages      Display pending messages
  memory        Print current memory usage
  mount         Mount storage
  prune         Prune expired records from catalog
  purge         Purge records from catalog
  quit          Terminate Bconsole session
  query         Query catalog
  restore       Restore files
  relabel       Relabel a tape
  release       Release storage
  reload        Reload conf file
  run           Run a job
  restart       Restart a job
  resume        Resume a job
  status        Report status
  stop          Stop a job
  setdebug      Sets debug level
  setbandwidth  Sets bandwidth
  snapshot      Handle snapshots
  setip         Sets new client address -- if authorized
  show          Show resource records
  sqlquery      Use SQL to query catalog
  time          Print current time
  trace         Turn on/off trace to file
  truncate      Truncate one or more Volumes
  unmount       Unmount storage
  umount        Umount - for old-time Unix guys, see unmount
  update        Update volume, pool or stats
  use           Use catalog xxx
  var           Does variable expansion
  version       Print Director version
  wait          Wait until no jobs are running

When at a prompt, entering a period cancels the command.

백업을 수행해보자. 위 bacula director 설정을 보면 설정되어 있는 job은 5개 이다. * bacula - backup - /etc * bacula - backup - /var/lib/bacula/bacula.sql * client1 - backup - /etc * client2 - backup - /etc * bacula - restore - /var/bacula-restores

run command 를 이용하여 아래 client1 서버에 백업 ᅟjob을 실행하였다. job을 선택하고 설정된 정보들을 확인한 후 ᅟ백업을 진행한다.

*run
A job name must be specified.
The defined Job resources are:
     1: bacula
     2: client1
     3: client2
     4: BackupCatalog
     5: RestoreFiles
Select Job resource (1-5): 2
Run Backup job
JobName:  client1
Level:    Full
Client:   client1-fd
FileSet:  Full Set
Pool:     File (From Job resource)
Storage:  File1 (From Job resource)
When:     2017-06-30 21:30:34
Priority: 10
OK to run? (yes/mod/no): yes
Job queued. JobId=47

백업이 진행되고 진행 상황을 확인해보는 방법은 2가지이다. status command 를 이용하면 Director 서비스의 상태를 확인 할 수 있는데 수행한 job 의 상태를 살펴보자.

*status dir
Running Jobs:
Console connected at 04-Jul-17 02:26
 JobId  Type Level     Files     Bytes  Name              Status
======================================================================
    73  Back Full          0         0  client1           is running
====

messages command 를 이용하면 수행되는 작업에 대한 메시지를 확인할 수 있다. autodisplay on 을 입력하면 엔터키를 입력할때마다 추가적으로 진행되는 메시지를 자동으로 보여준다.

*messages
30-Jun 21:30 bacula-dir JobId 47: Start Backup JobId 47, Job=client1.2017-06-30_21.30.36_09
30-Jun 21:30 bacula-dir JobId 47: Using Device "FileChgr1-Dev1" to write.
30-Jun 21:30 bacula-sd JobId 47: Volume "Vol-0016" previously written, moving to end of data.
30-Jun 21:30 bacula-sd JobId 47: Ready to append to end of Volume "Vol-0016" size=2,737,538
30-Jun 21:30 bacula-sd JobId 47: Elapsed time=00:00:01, Transfer rate=1.048 M Bytes/second
30-Jun 21:30 bacula-sd JobId 47: Sending spooled attrs to the Director. Despooling 117,536 bytes ...
30-Jun 21:30 bacula-dir JobId 47: Bacula bacula-dir 7.4.4 (202Sep16):
  Build OS:               x86_64-pc-linux-gnu debian 9.0
  JobId:                         47
  Job:                    client1.2017-06-30_21.30.36_09
  Backup Level:           Full
  Client:                 "client1-fd" 7.4.4 (202Sep16) x86_64-pc-linux-gnu,debian,9.0
  FileSet:                "Full Set" 2017-06-30 18:31:05
  Pool:                   "File" (From Job resource)
  Catalog:                "MyCatalog" (From Client resource)
  Storage:                "File1" (From Job resource)
  Scheduled time:         30-Jun-2017 21:30:34
  Start time:             30-Jun-2017 21:30:38
  End time:               30-Jun-2017 21:30:39
  Elapsed time:           1 sec
  Priority:               10
  FD Files Written:       509
  SD Files Written:       509
  FD Bytes Written:       994,569 (994.5 KB)
  SD Bytes Written:       1,048,682 (1.048 MB)
  Rate:                   994.6 KB/s
  Software Compression:   None
  Snapshot/VSS:           no
  Encryption:             no
  Accurate:               no
  Volume name(s):         Vol-0016
  Volume Session Id:      3
  Volume Session Time:    1498815750
  Last Volume Bytes:      3,799,870 (3.799 MB)
  Non-fatal FD errors:    0
  SD Errors:              0
  FD termination status:  OK
  SD termination status:  OK
  Termination:            Backup OK

30-Jun 21:30 bacula-dir JobId 47: Begin pruning Jobs older than 6 months .
30-Jun 21:30 bacula-dir JobId 47: No Jobs found to prune.
30-Jun 21:30 bacula-dir JobId 47: Begin pruning Files.
30-Jun 21:30 bacula-dir JobId 47: No Files found to prune.
30-Jun 21:30 bacula-dir JobId 47: End auto prune.

백업이 정상적으로 성공하였고 위 백업된 볼륨을 이용하여 복원을 시험한다. 가장 최근 진행한 백업을 선택하여 복원하는 방법으로 진행하였다. 복원 과정은 restrore 명령을 수행 후 원하는 item을 선택한다.

*restore
Using Catalog "MyCatalog"

First you select one or more JobIds that contain files
to be restored. You will be presented several methods
of specifying the JobIds. Then you will be allowed to
select which files from those JobIds are to be restored.

To select the JobIds, you have the following choices:
     1: List last 20 Jobs run # 마지막 20개 작업 나열
     2: List Jobs where a given File is saved # 파일이 백업된 장소로 작업 나열
     3: Enter list of comma separated JobIds to select # 작업별 ID 번호를 콤마 기호로 나열하여 입력
     4: Enter SQL list command # SQL 쿼리를 사용한 목록 나열
     5: Select the most recent backup for a client # 클라이언트의 가장 최근 작업을 선택
     6: Select backup for a client before a specified time # 클라이언트의 특정 시간대 이전 작업 선택
     7: Enter a list of files to restore # 복원할 파일명을 직접 입력
     8: Enter a list of files to restore before a specified time # 특정시간대에 이전한 백업된 파일 중 복원할 파일명을 직접 입력
     9: Find the JobIds of the most recent backup for a client # 클라이언트의 가장 최근 백업 ID 찾기
    10: Find the JobIds for a backup for a client before a specified time # 특정 시간대 이전 클라이언트의 가장 최근 백업 ID 찾기
    11: Enter a list of directories to restore for found JobIds # 특정 백업 ID에 백업된 디렉토리명 입력
    12: Select full restore to a specified Job date # 특정 일자로 전체 복원
    13: Cancel # 취소

아래의 경우 가장 최근 진행한 백업을 이용하여 복원하는 과정이며 위에서 진행하였던 client1 클라이언트에 최근 백업본을 복원하는 과정이다. 복원을 원하는 job을 선택하게 되면 ‘ls’ 와 같은 일반 리눅스 명령이 가능하여 디렉토리 이동 및 파일 목록을 확인 할 수 있다.

복원을 원하는 디렉토리 혹은 파일을 mark 명령으로 지정하게 되면 해당 디렉토리 혹은 파일이 복원할 준비가 되고 done 명령을 수행하면 복원할 내용을 확인 할 수 있다. 내용을 확인하고 문제가 없다면 그대로 복원이 진행된다. 클라이언트에 접속하여 복원이 잘 되었는지 확인한다.

Select item:  (1-13): 5
Defined Clients:
     1: bacula-fd
     2: client1-fd
     3: client2-fd
Select the Client (1-3): 2
Automatically selected FileSet: Full Set
+-------+-------+----------+----------+---------------------+------------+
| jobid | level | jobfiles | jobbytes | starttime           | volumename |
+-------+-------+----------+----------+---------------------+------------+
|    47 | F     |      509 |  994,569 | 2017-06-30 21:30:38 | Vol-0016   |
+-------+-------+----------+----------+---------------------+------------+
You have selected the following JobId: 47

Building directory tree for JobId(s) 47 ...  ++++++++++++++++++++++++++++++++++++++
428 files inserted into the tree.

You are now entering file selection mode where you add (mark) and
remove (unmark) files to be restored. No files are initially added, unless
you used the "all" keyword on the command line.
Enter "done" to leave this mode.

cwd is: /
$ ls
etc/
cd etc
cwd is: /etc/
$ ls
.pwd.lock
X11/
adduser.conf
adjtime
alternatives/
...
vim/
wgetrc
xdg/
$ cd ..
cwd is: /
$ ls
etc/
$ mark etc
509 files marked.

Invalid command "".  Enter "done" to exit.
$ done
Bootstrap records written to /var/lib/bacula/bacula-dir.restore.1.bsr
Bootstrap records written to /var/lib/bacula/bacula-dir.restore.1.bsr

The Job will require the following (*=>InChanger):
   Volume(s)                 Storage(s)                SD Device(s)
===========================================================================

    Vol-0016                  File1                     FileChgr1

Volumes marked with "*" are in the Autochanger.

509 files selected to be restored.

Run Restore job
JobName:         RestoreFiles
Bootstrap:       /var/lib/bacula/bacula-dir.restore.1.bsr
Where:           /var/bacula-restores
Replace:         Always
FileSet:         Full Set
Backup Client:   client1-fd
Restore Client:  client1-fd
Storage:         File1
When:            2017-06-30 21:46:37
Catalog:         MyCatalog
Priority:        10
Plugin Options:
OK to run? (yes/mod/no): yes
Job queued. JobId=48
*
You have messages.
*messages
30-Jun 21:46 bacula-dir JobId 48: Start Restore Job RestoreFiles.2017-06-30_21.46.39_10
30-Jun 21:46 bacula-dir JobId 48: Using Device "FileChgr1-Dev1" to read.
30-Jun 21:46 bacula-sd JobId 48: Ready to read from volume "Vol-0016" on file device "FileChgr1-Dev1" (/backup/file1).
30-Jun 21:46 bacula-sd JobId 48: Forward spacing Volume "Vol-0016" to file:block 0:2737538.
30-Jun 21:46 bacula-sd JobId 48: End of Volume at file 0 on device "FileChgr1-Dev1" (/backup/file1), Volume "Vol-0016"
30-Jun 21:46 bacula-sd JobId 48: End of all volumes.
30-Jun 21:46 bacula-sd JobId 48: Elapsed time=00:00:01, Transfer rate=1.048 M Bytes/second
30-Jun 21:46 bacula-dir JobId 48: Bacula bacula-dir 7.4.4 (202Sep16):
  Build OS:               x86_64-pc-linux-gnu debian 9.0
  JobId:                  48
  Job:                    RestoreFiles.2017-06-30_21.46.39_10
  Restore Client:         client1-fd
  Start time:             30-Jun-2017 21:46:41
  End time:               30-Jun-2017 21:46:41
  Files Expected:         509
  Files Restored:         509
  Bytes Restored:         994,569
  Rate:                   0.0 KB/s
  FD Errors:              0
  FD termination status:  OK
  SD termination status:  OK
  Termination:            Restore OK

30-Jun 21:46 bacula-dir JobId 48: Begin pruning Jobs older than 6 months .
30-Jun 21:46 bacula-dir JobId 48: No Jobs found to prune.
30-Jun 21:46 bacula-dir JobId 48: Begin pruning Files.
30-Jun 21:46 bacula-dir JobId 48: No Files found to prune.
30-Jun 21:46 bacula-dir JobId 48: End auto prune.

복원이 성공하였다면 복원한 Client 서버에 접속하여 복원된 파일이 문제가 없는지 점검한다.

# cd /var/bacula-resotres
# ls
total 12
drwxr-xr-x  3 root root 4096 Jun 30 21:46 .
drwxr-xr-x 12 root root 4096 Jun 30 21:46 ..
drwxr-xr-x 62 root root 4096 Jun 30 15:16 etc
# pwd
/var/bacula-restores/etc

Bacula Web

Bacula web 은 Bacula 에서 수행한 작업의 상태, 작업 로그, 백업 레포트 등등 다양한 정보를 제공하는 PHP 웹 기반 모니터링 도구이다. Bacula web 은 GPL v 2.0 license 무료로 사용 가능하고 가장 큰 장점은 설치가 쉽고 구성이 쉽다. 디자인 완성도는 개인적으로 부족하다고 느끼지만 직관적인 인터페이스를 가지고 있어 백업 모니터링 도구의 역할로써는 매우 훌륭하다고 생각한다.

Bacula web ᅟ의 기능을 간략하게 소개한다.

  • 작업 보고서 페이지 제공
  • Dashboard 제공
  • 클라이언트 백업 레포트 기능
  • 백업 작업 레포트 기능
  • Pool 및 Voulme 레포트 기능
  • 작업 로그 확인 가능
  • 필터 및 옵션 설정 가능

Install Bacula Web

Director 서비스가 구동되는 bacula server(debian stretch) 에서 설치를 진행하였다. 당연한 이야기지만 bacual web 을 설치하기 전 bacula server 구성이 완료되어야 정상적으로 설치가 가능하다는 것을 참고하자.

ᅟInstsall Nginx and PHP

Bacual web 은 php 로 개발되어 웹 서버와 php 를 설치해야 한다. apt 를 이용하여 nginx, php-fpm 과 관련 패키지를 설치한다.

# apt update
# apt install nginx apache2-utils php-fpm php-pgsql php-gd

Configure PHP-FPM

php 의 timezone 설정을 한다. ᅟdate.timezone 문구를 찾아 아래와 같이 수정한다.

# vi /etc/php/7.0/fpm/php.ini
...
- ;date.timezone =
+ date.timezone = Asia/Seoul

php-fpm 서비스를 재시작한다.

# systemctl restart php7.0-fpm

Configure Nginx

웹 서버로 사용할 ᅟnginx 설정을 한다. bacula web 을 개인적으로 매우 훌륭하다고 하였지만 아쉬운 부분이자 단점인 로그인 기능이 없다. 그래서 apache 의 웹 인증을 적용하기로 하였다. htpasswd 를 이용하여 bacula web 페이지에 접속시 admin 계정으로만 접근이 가능하도록 설정한다.

# htpasswd -c /etc/nginx/htpasswd.users admin
New password:
Re-type new password:
Adding password for user admin

이제 nginx 의 사이트 설정을 한다. default configuration 파일을 수정한다. 접속할 ip 또는 도메인 정보를 server_name 항목에 입력하고 웹소스 경로를 설정한다. 추가로 php 와 htpasswd 설정을 추가한다.

# vi /etc/nginx/sites-available/default
...
server {
    listen 80 default_server;
    listen [::]:80 default_server ipv6only=on;

    root /var/www/bacula-web;
    index index.php index.html index.htm;

    server_name 192.168.0.190;

    auth_basic "Backup Monitoring";
    auth_basic_user_file /etc/nginx/htpasswd.users;

    location / {
        try_files $uri $uri/ =404;
    }

    error_page 404 /404.html;
    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
        root /usr/share/nginx/html;
    }

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/run/php/php7.0-fpm.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

Download and Configure Bacula Web

nginx 에 설정한 웹소스 경로에 bacula web 압축 파일을 다운받아 해당 경로로 압축을 푼다. 현재 최신 버전은 7.4.0 이다.

# cd /opt
# wget --content-disposition http://www.bacula-web.org/download.html?file=files/bacula-web.org/downloads/7.4.0/bacula-web-7.4.0.tgz
# mkdir /var/www/bacula-web
# tar xvfz bacula-web-7.4.0.tgz -C /var/www/bacula-web

압축을 풀었다면 소스 디렉토리로 이동하여 설정을 진행한다. config.php.sample 파일을 복사 후 bacula catalog 정보에 맞게 정보를 수정한다.

# cd /var/www/bacula-web
# cd application/config
# cp config.php.sample config.php
# vi config.php
...
// PostgreSQL bacula catalog
$config[0]['label'] = 'Bacula Server';
$config[0]['host'] = 'localhost';
$config[0]['login'] = 'bacula';
$config[0]['password'] = 'vmffpdlsxprtmxm1!';
$config[0]['db_name'] = 'bacula';
$config[0]['db_type'] = 'pgsql';
$config[0]['db_port'] = '5432';

웹소스 경로의 소유권을 nginx 를 구동하는 www-data 로 변경한다.

# chown -R www-data /var/www/bacula-web

Access Bacula Web

위 순서로 설치를 마치고 이제 브라우저에서 bacula web 으로 접속해본다. nginx 에서 설정한 도메인 또는 IP 로 접속한다. 웹 인증 창이 뜨고 인증에 성공하면 아래와 같이 bacula web 페이지를 확인 할 수 있다.

Bacula Report

Bacula 에서는 messages 기능을 이용하여 job 마다 메일로 레포트를 받을 수 있다. 하지ᅟ만 job에 개수만큼 메일을 수신 받다보니 비효율적으로 관리가 될 수 있다. 찾아보니 서드 파티로 perl 스크립트를 이용하여 정해진 기간내에 수행된 백업 결과를 정리된 표형식으로 받을 수 있다.

Install Bacula Report

설치는 매우 간단하다. 소스가 github 에 공개되어 있고 INSTALL 파일에 설치 및 ᅟ설정 방법에 대하여 자세하게 나와있다. git 을 이용하여 소스를 다운받는다.

# cd /etc/bacula/scripts
# git clone https://github.com/davidegiunchi/send-bacula-backup-report.git
# cd send-bacula-backup-report
# less INSTALL

Settings Bacula Report

INSTALL 파일을 보면 상세하게 설정 방법이 나와있다. 정상적으로 이 스크립트를 이용하기 위해서는 bacula 서버가 ᅟ운영되고 있어야 하고 db 는 mysql 또는 postgresql 을 사용해야 한다. 추가로 로컬 서버에 ᅟmta 설치 및 구성이 되어있어야 한다. perl 스크립트를 사용하기에 perl 모듈을 설치한다.

# apt update
# apt install -y libhtml-template-perl libmime-lite-perl
If you have a MySQL db:
# apt install -y libdbd-mysql-perl
If you have a PostgreSQL db:
# apt install -y libdbd-pg-perl

설정 파일을 복사하고 권한을 수정한다.

# cp send_bacula_backup_report.pl /etc/bacula/scripts/
# cp send_bacula_backup_report.conf.example /etc/bacula/send_bacula_backup_report.conf
# cp send_bacula_backup_report.tpl /etc/bacula/
# chmod 750 /etc/bacula/scripts/send_bacula_backup_report.pl
# chmod 640 /etc/bacula/send_bacula_backup_report.conf

설정 파일을 수정한다. db type(데이터베이스 타입), db password(데이터베이스 패스워드), email_from(받는 이메일 주소), email_to(보내는 이메일 주소) bacula web 이 설치되어 있다면 웹 경로의 도메인을 수정한다. 이 역할은 메일에서 각 job 의 상태를 클릭하게 되면 해당 bacula web 페이지로 이동하여 정보를 확인할 수 있게 된다.

# vi /etc/bacula/send_bacula_backup_report.conf
...
# mysql or Pg
- $db_type='mysql';
+ $db_type='Pg';
# other db parameters
$db_host='localhost';
$db_name='bacula';
- $db_username='root';
+ $db_username='bacula';
- $db_password='xxxxxxxx';
+ $db_password='vmffpdlsxprtmxm1!';
# url to bweb.pl or your bacula's web interface. If you don't have a web interface, you can skip it
- $bweb_path='http://hostname.or.ip.of.the.bacula.dir/';
+ $bweb_path='http://backup.iorchard.co.kr';
# email arguments
- $email_from='backup@domain.com';
+ $email_from='backup@iorchard.co.kr';
- $email_to='ced@domain.com';
+ $email_to='sungmin@iorchard.co.kr';
$email_subject="Backup report of $mday/$mon/$year";

$template_file='/etc/bacula/send_bacula_backup_report.tpl';

postgresql 을 사용한다면 아래 작업을 추가해줘야 한다.

# su postgresql
$ psql bacula < ./functions-postgresql.sql

설정은 끝났고 정상적으로 잘 발송이 되어지는지 시험해본다. 뒤에 숫자는 하루동안 수행된 job 에 대한 정보를 취합하겠다는 인수이다. ‘2’ 는 2일, ‘3’ 는 3일이 된다.

# /etc/bacula/scripts/send_bacula_backup_report.pl 1

정상적으로 스크립트가 수행되었다면 아래와 같이 한눈에 ᅟ보기 쉬운 백업 레포트를 메일로 받을 수 있다. 위에서 설명한 바와 같이 상태 값을 클릭하게 되면 bacual web 모니터링 페이지로 이동한다.

crontab 설정에 위 스크립트를 스케쥴에 맞게 레포트가 발송 될 수 있도록 예약 작업을 추가한다. 아래 기본 예제는 ᅟ1일 레포트를 월요일부터 목요일까지 매일 받고 일요일날 3일 레포트를 받도록 설정하였다. 기본 예제이지만 풀백업은 시간이 더 소요되므로 금요일날 풀백업을 진행하여 일요일날 레포트를 받고 월요일에서 목요일은 증분백업을 진행하여 하루마다 레포트를 받는 것으로 아래와 같이 설정할 수 있다.

# crontab -e
...
# send daily bacula backup report
0 9    * * 2-5 root    /etc/bacula/scripts/send_bacula_backup_report.pl 1 >/dev/null
0 9    * * 1   root    /etc/bacula/scripts/send_bacula_backup_report.pl 3 >/dev/null
]]>
Mon, 03 Jul 2017 00:00:00 +0900
http://www.iorchard.net/2017/06/26/kubic_안양_bind_성능_문제_해결.html http://www.iorchard.net/2017/06/26/kubic_안양_bind_성능_문제_해결.html <![CDATA[KUBIC 안양 BIND 성능 문제 해결]]> KUBIC 안양 BIND 성능 문제 해결

뭐라 시작해야 할 지 모르겠다.

지난 3월부터 계속된 안양 고객 KUBIC 구축이 드디어 상용 서비스에 들어가기 직전이다.

3개월이 넘는 긴 여정을 이제야 마무리하게 되었다.

문제점 recap

KUBIC 구축 대상 팀은 BIND를 이용하여 DNS 서비스를 한다. 상용 부하를 주면서 고객이 사용하는 health check 프로그램이 dns query를 던졌을 때 하나라도 응답이 오지 않으면 실패로 간주한다.

고객이 정한 상용 서비스 투입을 위한 최소 rps(requests per second)값은 30,000이다. 즉, 30000 rps이상에서 실패가 없어야 KUBIC을 사용할 수 있다.

kubernetes 부하 분산 기능을 담당하는 kube-proxy의 iptables 모드가 incoming packet에 대한 SNAT으로 인해 source port exhaustion(원본 포트 고갈) 문제가 발생하여 상용 적용할 수가 없었다.

해결책

kubernetes 1.5부터 beta 기능으로 OnlyLocal 기능이 포함되었다. 아직 beta이긴 하지만 시험을 해 보았다.

svc_aaa.yml:

kind: Service
metadata:
  name: aaa
  annotations:
    service.beta.kubernetes.io/external-traffic: OnlyLocal
  namespace: default
  labels:
    app: aaa
spec:
  selector:
    app: aaa
  ports:
  - port: 53
    name: dns-udp-53
    targetPort: 53
    nodePort: 30053
    protocol: UDP
  - port: 53
    name: dns-tcp-53
    targetPort: 53
    nodePort: 30053
    protocol: TCP
  type: NodePort

kubectl create -f svc_aaa.yml 하고 서비스 상태를 보자.

kubectl get svc:

NAME         CLUSTER-IP    EXTERNAL-IP   PORT(S)                     AGE
aaa          10.24.15.76   <nodes>       53:30053/UDP,53:30053/TCP   3s

우리가 원하는 udp/tcp port 30053으로 listen하고 있는 것을 볼 수 있다.

이제 dns query를 하고 tcpdump로 결과를 보면 incoming SNAT을 하지 않는 것을 볼 수 있다.

dig @iia2 -p 30053 +short cnn.com:

151.101.193.67
151.101.65.67
151.101.1.67
151.101.129.67

tcpdump -i docker0 host 172.17.0.1:

15:30:19.012211 IP 10.0.24.51.57237 > 172.17.0.1.domain: 27422+ [1au] A? cnn.com. (36)
15:30:19.012611 IP 172.17.0.1.domain > 10.0.24.51.57237: 27422 4/4/8 A 151.101.65.67, A 151.101.129.67, A 151.101.1.67, A 151.101.193.67 (384)

위 tcpdump 결과를 보듯이 원본 IP:port(10.0.24.51은 dig 명령을 내린 서버의 IP)가 바뀌지 않는다.

실험은 성공적이었고, 바로 안양 KUBIC 서버에 위 내용을 적용하고 고객에게 상용 부하 적용을 요청하였다.

적용 결과

1개 POD 적용

먼저 POD 한 개로 부하 시험을 하였다. 10,000 rps이상 부하를 주어도 health check는 실패하지 않았다.

4개 POD 적용

POD를 4개로 scale up하였다. 23,000 rps까지 부하를 주었는데 역시 health check는 실패하지 않았다.

왜 23,000까지만 시험했는가?

현재의 DNS 부하가 많지 않아 L4에서 한 서버에 줄 수 있는 부하가 23,000까지밖에 되지 않는다고 한다.

DNS부하가 밤 10시 이후부터 많아지기 때문에 30,000이상을 주려면 그 때 해 보아야 한다고 한다.

30,000을 주지 않았으나 위 논리 상 결국 문제는 없어 보인다. 이전에는 6,000 - 7,000 정도에서 문제가 발생하였다.

나중에 30,000이상 부하를 주는 실험을 하면 결과를 알려 달라고 하였다.

에필로그

3개월 이상 지속된 이번 안양 KUBIC 프로젝트는 이번 주 마무리됩니다.

그 동안 함께 고생한 안양 이현우 책임님에게 감사드립니다. 이런 저런 대화에서 배운 것도 많았고, 기술적 대화의 충만함으로 길었던 본 프로젝트를 무사히 마무리할 수 있었던 것 같습니다.

본 프로젝트 일정이 많이 연장되었음에도 기다려 준 점에 대해서도 감사합니다.

다시 한번 강조하지만 KUBIC은 프로그램 이 아니라 프로세스 입니다.
(KUBIC is NOT a program BUT a process.)
]]>
Mon, 26 Jun 2017 00:00:00 +0900
http://www.iorchard.net/2017/06/20/porch_centos_6_지원.html http://www.iorchard.net/2017/06/20/porch_centos_6_지원.html <![CDATA[PORCH CentOS 6 지원]]> PORCH CentOS 6 지원

PORCH 현 구축 사이트에는 HP 구형 장비가 많이 있다. 이 장비들을 활용하려다 보니 최신 OS와는 호환이 되지 않는다.

HP Proliant DL380 G4/G5/G6와 같은 장비는 CentOS 7과 호환이 잘 되지 않아 CentOS 6를 설치하기로 했다.

PORCH는 CentOS 7, Debian Jessie만 현재 지원한다. CentOS 6도 지원하자.

base 이미지 만들기

/srv/fai/config/basefiles/ 에서 CentOS 6 base image를 만들자.

$ cd /srv/fai/config/basefiles
$ ./mk-basefile CENTOS6_64

상당한 시간이 걸릴 것이다. CENTOS6_64.tar 파일이 생성된다. 이 파일을 xz로 압축하자.

$ xz CENTOS6_64.tar

이제 CENTOS6_64.tar.xz 파일이 생성된다. 크기는 대략 45MiB 이다.

FAI config tarball 만들기

위 base image를 포함한 FAI config tarball을 만들자.

/srv/fai/config/config_tarball.sh:

tar cvf fai-CENTOS6-config.tar class debconf disk_config files hooks
package_config scripts pubkeys saltkey basefiles/CENTOS6_64.tar.xz


$ sh config_tarball.sh

이제 fai-CENTOS6-config.tar 파일을 /srv/tftp/fai/ 로 옮긴다.

$ mv fai-CENTOS6-config.tar /srv/tftp/fai/

PORCH 설정

PORCH에서 CENTOS6용 svc-interface 템플릿 파일을 만들자. CENTOS7과 같으므로 복사하고 uwsgi를 재시작한다.

$ cd ~/porch/porch/templates
$ cp svc-interface-CENTOS7.j2 svc-interface-CENTOS6.j2
$ sudo systemctl restart uwsgi

PORCH 코드 수정

기존 PORCH 코드는 OS 지원 목록을 client에 저장하였다.(config.js) 이것은 별로 좋지 않다. client는 PORCH 서버에게 물어보는 것이 낫다.

Client: “당신은 어떤 OS 자동설치를 지원합니까?”
PORCH: “저는 Debian jessie, CentOS 6, CentOS 7을 지원합니다.”

위와 같이 되도록 코드를 수정하였다.

그리고 각 구축 사이트 별로 사용할 OS만 노출시키면 된다.

~/porch/porch/config.py:

# Supported OS List
SUPPORTED_OS = ['JESSIE', 'CENTOS7', 'CENTOS6', 'UXENOS']

API 코드 등록:

/api/machine/supported_os

수정된 클라이언트 코드를 rebuild하자.

$ cd porch/quasar/porch
$ quasar build
$ cp -a dist/* ~/porch/web/

가산 작업

가산 KIDC에 있는 HP 머신 자동 설치를 지원하였다. Gres25와 Gres33을 PORCH로 자동 설치하였다. (호스트명에 G를 넣은 것은 아마도 가산(Gasan)의 첫글자를 딴 것이리라.)

  • Gres25: HP Proliant DL380 G6, CentOS 6 설치
  • Gres33: HP Proliant DL380 G7, CentOS 7 설치

네트워크 정보는 민감한 내용이므로 적지 않겠다.

오늘의 미션은

안양에 있는 PORCH 서버를 이용하여 가산 KIDC에 있는 머신에 WAN구간을 통해 OS와 App 자동 배포를 한다.

이다.

BIND App 배포 문제

WAN을 통한 App 배포를 지원하기 위해 ansible 환경에 proxy 설정을 PORCH서버의 외부 IP로 수정하였다.

첫 번째 문제 해결

PORCH client는 App/File 배포 시 배포대상서버의 배포망 IP로 접속하여 작업을 수행한다. App 배포는 ansible 작업, File 배포는 rsync 작업이다.

문제는 WAN 건너편에 있는 배포대상서버의 경우 설치 완료 후에는 배포망 IP로 접속할 수 없다는 것이다. 배포대상서버의 기본 gateway가 서비스망 router이다. 패킷이 배포망으로 왔는데, 응답 패킷이 서비스망으로 전달이 되지 않기 때문이다. 물론 kernel parameter에 ip_forward를 enable하면 되지만 일반 서버인데 보안 상 그런 설정은 좋지 않다.

그래서 PORCH client의 코드를 수정하였다. App/File 배포를 위한 접속 우선순위를 서비스망, 배포망 순서로 변경하였다. 즉, 서비스망 IP가 있으면 서비스망 IP로 접속하고, 없으면 배포망 IP로 접속한다.

그러나 아직 수정된 코드를 git commit하지는 않았다. 좀 더 신중히 생각하여 commit 여부를 결정할 것이다.

두 번째 문제 해결

Gres25에 BIND App 배포할 때 마지막 named를 띄우는 작업에서 실패한다. 왜? named를 띄우는 작업은 systemd를 이용하도록 했는데 CentOS 6는 systemd가 없다. systemd는 CentOS7부터 지원한다.

따라서 현재의 BIND App playbook을 다음과 같이 수정하였다.

~/porch/porch/templates/bind_main.j2:

...
- name: Add named to rc.local (CentOS 6)
  lineinfile:
    name: /etc/rc.local
    line: "/usr/sbin/named -u named"
  when:
    - ansible_distribution == "CentOS"
    - ansible_distribution_major_version == "6"
- name: Run named (CentOS 6)
  command: /usr/sbin/named -u named
  when:
    - ansible_distribution == "CentOS"
    - ansible_distribution_major_version == "6"
- name: Copy named.service systemd file.
  copy:
    src: named.service
    dest: /etc/systemd/system/named.service
  when:
    - ansible_distribution == "CentOS"
    - ansible_distribution_major_version == "7"
- name: Run named service
  systemd:
    name: named
    daemon-reload: yes
    enabled: yes
    state: started
  when:
    - ansible_distribution == "CentOS"
    - ansible_distribution_major_version == "7"

위와 같이 배포판 버전 별로 시작하는 방법을 다르게 하여 하나의 App으로 CentOS 6/7 머신에 배포하도록 하였다.

Gres25/33에 BIND App 배포 시험 완료!

]]>
Tue, 20 Jun 2017 00:00:00 +0900
http://www.iorchard.net/2017/06/15/porch_wan_구간지원_2.html http://www.iorchard.net/2017/06/15/porch_wan_구간지원_2.html <![CDATA[PORCH WAN 구간지원 2]]> PORCH WAN 구간지원 2

문제점

지난 1차 문서에서 WAN구간 서버 OS 자동 설치 세팅을 하였으나 서버가 준비되지 않아 시험하지 못했다. 오늘 시험을 해 보았으나 다음과 같은 문제가 발생하였다.

dracut Warning: Could not boot.
dracut Warning: /dev/root does not exist

DHCP서버로부터 IP는 잘 받아오고, kernel/initrd 이미지도 http로 잘 받아 부팅하는 중 위 에러가 나오고 서버는 멈춘다.

문제는 squashfs image를 못받아오는 것 같다. 이유는? 모른다.

아래는 해당 머신의 pxeboot 설정이다.

default porch-generated
label porch-generated
kernel http://<porch_service_ip>/api/fai/boot/vmlinuz-3.16.0-4-amd64
append initrd=http://<porch_service_ip>/api/fai/boot/initrd.img-3.16.0-4-amd64
ip=dhcp
root=live:http://<porch_service_ip>/api/fai/boot/squash.img aufs
FAI_FLAGS=verbose,sshd,reboot
FAI_CONFIG_SRC=http://<porch_service_ip>/api/fai/boot/res82.tar
FAI_ACTION=install  console=tty0 console=ttyS0,115200n8
net.ifnames=0 edd=off

append 라인부터 마지막 라인까지는 원래 한 줄로 길게 쓰여진 것이지만 가독성을 위해 개행문자로 처리했다.

squash.img 도 http로 porch의 service ip로부터 가져오도록 잘 설정되어 있다.

실험1

HP Proliant Gen5 서버에서 위 문제를 시험하고 있었다. Gen6도 그런지 같은 시험을 하였으나 역시 같은 결과가 나왔다. Gen6 머신은 내부 배포망에서 UDAM으로 잘 설치되었던 서버이다.

실험2

Gen6 서버에 대해 pxelinux.cfg의 설정 파일을 다음과 같이 고정 IP 방식으로 수정하였다.

ip=203.248.241.10::203.248.241.1:255.255.255.240:res83:eth0:none:164.124.101.2

이전에는 ip=dhcp 로 dhcp로부터 IP 설정을 하는 방식으로 했는데, 위와 같이 static IP를 설정하도록 수정하였다.

결과는? 아~~ 잘 된다.

그렇다면, ip=dhcp 로 하면 문제라는 것으로 귀결된다.

PXE로 OS 설치하는 절차에서 총 3번의 DHCP 요청을 한다.

  1. 머신 최초 pxeboot 단계
  2. ipxe boot image를 이용한 pxeboot 단계
  3. kernel/initrd image를 가져와 부팅하는 단계

위 실험에 의하면 첫 두번의 DHCP 요청은 성공하고, 세번째 요청에서 DHCP 요청이 실패한다고 할 수 있다. 왜일까?

이런 의문점을 안고 오늘은 이곳을 떠났다.

]]>
Thu, 15 Jun 2017 00:00:00 +0900
http://www.iorchard.net/2017/06/13/kube_router에_대한_생각.html http://www.iorchard.net/2017/06/13/kube_router에_대한_생각.html <![CDATA[kube-router에 대한 생각]]> kube-router에 대한 생각

아래 kube-router URL입니다.

https://github.com/cloudnativelabs/kube-router

결론부터 말하자면, kube-router를 통한 성능 향상의 가능성은 높아 보입니다. 물론 실험을 해야 확실히 말할 수 있겠지요. 그러나 제가 기술한 kube-proxy의 문제점이 kube-router에서는 개선될 것으로 보입니다.

kube-router는 ipvs 기술을 이용합니다. ipvs는 LVS 에서 사용하는 기술인데요. ipvs는 kube-proxy가 사용하는 기술인 iptables보다 성능이 우수합니다. ipvs는 hashing기술을 이용하지만, iptables는 chain을 이용합니다. hashing이 chain보다 성능이 우수합니다.

그리고 확실히 중요한 성능 우위 요인은

성능의 발목을 잡았던 kube-proxy의 incoming SNAT이 ipvs에는 없습니다.

그리고 덧붙여 서비스 개수가 늘면 늘수록 iptables는 ipvs에 비해 기하급수적으로 성능 저하가 발생합니다. (hashing vs chain 논리가 여기서도 적용됩니다.) 대규모의 kubernetes cluster 서비스를 한다면, 어차피 서비스 개수가 늘어나는데, 결국 기존의 kube-proxy가 사용하는 iptables 로는 scale-out이 어렵다고 보아야 하겠습니다.

그러면, 왜 kube-proxy에 ipvs를 이용하는 코드를 넣지 않을까요? kube-proxy의 proxy mode는 현재 userspace 와 iptables 모드가 있습니다. 처음에 userspace모드만 있었는데, 성능 문제로 iptables 모드가 추가되었습니다. 이제 iptables 성능 문제로 ipvs 모드를 추가하면 될 것 같은데요. 이유는 저도 모르겠네요.

아무튼 결론을 다시 말하자면,

kube-router의 기반 기술(ipvs) 적용으로 성능 향상을 가질 수 있다고 봅니다.

It’s worth to give it a try!!!

]]>
Tue, 13 Jun 2017 00:00:00 +0900
http://www.iorchard.net/2017/06/08/porch_wan_구간_지원_1.html http://www.iorchard.net/2017/06/08/porch_wan_구간_지원_1.html <![CDATA[PORCH WAN 구간 지원 1]]> PORCH WAN 구간 지원 1

PORCH WAN support 논의

목표는 WAN 건너 원격 서버를 PORCH으로 OS/APP 설치하고 싶다. 과연 PORCH이 WAN 구간에서도 제대로 작동하게 할 수 있는가?

PORCH은 최초 설계 시 WAN 지원을 위해 LAN-specific protocol을 지양하였다. PORCH이 사용하는 protocol을 정리한다.

Protocol lists:

  • bootpc : udp 68, client-side bootstrap protocol (pxeboot) 클라이언트가 udp 68을 source port로 하여 dhcp 요청을 한다.
  • bootps : udp 67, server-side bootstrap protocol (dhcpd) 클라이언트에게 IP를 할당하고, bootstrap image file의 위치를 알려준다.
  • tftp: tcp 69, trivia l file transfer protocol (tftp-hpa) 클라이언트는 bootstrap image를 tftp를 이용해 가져온다. bootstrap image에는 kernel/initrd 위치가 정의되어 있다.
  • http: tcp 80, hypertext transfer protocol (nginx) 클라이언트는 kernel, initrd 파일을 가져와 부팅하고, squashfs 파일을 가져와 설치를 위한 root filesystem으로 마운트한다.

위 과정 중 WAN 구간에서 문제가 되는 protocol은 bootps이다. DHCP는 L2 level protocol이기 때문에 WAN 구간을 거쳐 사용할 수 없다. 나머지 protocol은 WAN 구간에서 문제가 없는 protocol이다. DHCP WAN 지원을 위해 dhcp relay를 이용하기로 했다.

target server --- dhcp relay --- WAN --- PORCH (dhcp server)
            broadcast          unicast

target server는 DHCP request broadcast를 한다. dhcp relay는 DHCP request를 PORCH에 전달한다. PORCH은 target server의 MAC주소가 등록이 되어 있으면 설정된 IP를 보낸다. dhcp relay는 받은 IP정보를 target server에 전달한다. 특별할 것이 없다. 잘 작동할 것이다. 위와 같이 작동되도록 설정만 잘 하면 된다.

PORCH 설정

  1. /etc/dhcp/dhcpd.conf 변경

    당연히 dhcpd.conf파일을 WAN에서도 잘 되도록 수정해야 할 것이다. 두 개의 subnet block을 추가하자. /etc/dhcp/dhcpd.conf

    subnet x.x.x.0 netmask 255.255.255.0 {
    }
    

    이것은 PORCH서버의 서비스 네트워크 대역이다.(보안 상 네트워크 정보는 숨긴다.) WAN으로부터 dhcp request를 받아야 하므로 서비스 네트워크 대역도 listen하기 위해 빈 subnet 정의를 한다.

    subnet y.y.y.0 netmask 255.255.255.0 {
      option routers y.y.y.1;
      option domain-name "";
      option domain-name-servers 164.124.101.2;
      option ntp-servers udam;     # RFC1035 (port 123)
      server-name udam;
      next-server <udam_service_ip>;
      if exists user-class and option user-class = "iPXE" {
        filename "http://<udam_service_ip>/api/fai/boot/";
      } else {
        filename "fai/undionly.kpxe";
      }
    }
    

    위와 같이 target 서버의 subnet 정의를 한다. PORCH은 이 대역의정보를 보내줄 것이다.

  2. /etc/default/isc-dhcp-server

    PORCH에서의 DHCP 기본 설정은 배포망 인터페이스만 listen하는 것이다. WAN 지원을 위해서는 서비스망 인터페이스도 listen해야 한다.

    INTERFACES=”eth0 eth1”

    • eth0: 배포망
    • eth1: 서비스망
  3. 파일 다운로드 URL 수정

    PORCH에서 제공하는 파일들(kernel, initrd, squashfs)의 URL은 기본적으로 배포망 IP로 설정되어 있다. WAN지원을 위해 이것을 서비스망 IP로 변경해야 한다. 한가지 걸리는 문제는 서비스망 IP로 변경되기 때문에 배포망에 있는 서버조차도 PORCH의 서비스망 IP에 접속해 파일들을 가져옴으로써 배포망의 효용성이 떨어진다.그러나 이것은 WAN지원을 위해 희생이 되어도 되는 작은 문제이다.

    1. /srv/tftp/fai/boot.ipxe (chain 라인 수정)
    2. PORCH_HOME/porch/porch/config.py (PORCH_URL 수정)

    위 두 개의 파일을 수정한 후 uwsgi를 restart한다.

    $ sudo systemctl restart uwsgi

시험

  • 기존 배포망에 있던 서버에 대한 OS 배포 시험: 문제 없음 확인
  • WAN 건너편에 있는 서버에 대한 OS 배포 시험: 준비가 안 되어 시험하지 못함

이현우/소철 부장님이 시험해 보고 문제있으면 알려주겠다고 한다.

그리고  난 이 곳을 떠났다…

]]>
Thu, 08 Jun 2017 00:00:00 +0900
http://www.iorchard.net/2017/05/20/k8s_dns_perf_problem.html http://www.iorchard.net/2017/05/20/k8s_dns_perf_problem.html <![CDATA[Performance problem with Kubernetes-based BIND container]]> Performance problem with Kubernetes-based BIND container

kubernetes 기반 BIND DNS container 서비스 프로젝트를 하고 있다.

성능 상 문제가 있어 여기에 기술한다.

문제 원인

BIND DNS 서비스는 가벼운 서비스이기 때문에 kubernetes 기반으로 빠르고(agile) 탄력적인(elastic) 서비스를 하기에 적합하다고 보았다.

그러나 성능면에서 문제가 발생하였다.

성능 실험은 다음과 같이 한다. L4에 DNS container의 external ip를 등록하고 실제 트래픽을 흐르게 한다. 그리고 health check program은 주기적으로 100개의 query를 던져 몇 개가 응답하는 지 본다. 100개 다 응답하면 healthy한 것이고, 하나라도 응답이 안되면 상용 서비스 적용 실패로 간주한다. L4에서 DNS container에 가중치를 증가시키면서 requests/seconds (rps)의 한계를 시험한다.

성능 실험 결과는 다음과 같이 나왔다.

실제 DNS 트래픽을 흘려 보면, 1개의 container가 적정 1000 ~ 1200 rps 정도까지만 처리가 가능하다. 2500 rps를 흘리면, health check 실패가 나온다. 물리 서버로 시험하면 10000-12000 rps 가 가능하다고 하니 차이가 크다.

수작업으로 넣은 iptables 는 성능이 잘 나온다. (DNAT 룰 적용) 그러나 kube-proxy가 넣은 iptables는 성능이 잘 나오지 않는다. 뭐가 다른가 분석이 필요하다. 그래서 수사망은 iptables의 성능 문제로 좁혀졌다.

실험 1. kubedns가 영향을 주는가?

먼저 클러스터 내부에 서비스하는 kubedns가 영향을 주는 것이 아닌가 하는 의심이 있어 확인차 다음과 같이 실험했다.

BIND container를 하나만 켜고, 외부에서 cnn.com 을 query해 보았다.

외부 서버$ dig @ cnn.com

다음은 컨테이너가 물린 virtual nic interface의 tcpdump 결과이다.

# tcpdump -n -i vethf83f700 udp port 53
13:58:16.615455 IP 10.100.13.1.46454 > 10.100.13.2.domain: 4528+ [1au] A? cnn.com. (36)
13:58:16.616294 IP 10.100.13.2.41548 > 192.41.162.30.domain: 46593 [1au] A? cnn.com. (48)
13:58:16.814478 IP 192.41.162.30.domain > 10.100.13.2.41548: 46593-| 0/7/2 (510)
13:58:17.247744 IP 10.100.13.2.38443 > 205.251.192.47.domain: 13685 [1au] A? cnn.com. (48)
13:58:17.250624 IP 205.251.192.47.domain > 10.100.13.2.38443: 13685*- 4/4/1 A 151.101.129.67, A 151.101.193.67, A 151.101.1.67, A 151.101.65.67 (236)
13:58:17.251015 IP 10.100.13.2.domain > 10.100.13.1.46454: 4528 4/4/8 A 151.101.65.67, A 151.101.1.67, A 151.101.193.67, A 151.101.129.67 (384)

위 dump에서 보이는 각 IP는 다음과 같이 해석된다.

- 10.100.13.1 : docker0
- 10.100.13.2 : bind container
- 192.41.162.30 : l.gtld-servers.net (미국에 있는 .com 관리 네임서버)
- 205.251.192.47 : ns-47.awsdns-05.com. (cnn.com의 네임서버)

위 dump를 보면 BIND가 자체적으로 recursive query를 수행하는 것을 볼 수 있다.

다시 한번 같은 query를 하면,

14:12:19.187171 IP 10.100.13.1.52585 > 10.100.13.2.domain: 32962+ [1au] A?
cnn.com. (36)
14:12:19.187725 IP 10.100.13.2.domain > 10.100.13.1.52585: 32962 4/4/8 A
151.101.1.67, A 151.101.129.67, A 151.101.65.67, A 151.101.193.67 (384)

바로 cache된 데이터를 리턴하는 것을 볼 수 있다.

BIND container 성능과 kubedns는 무관함을 보여준다. 물론 kubedns를 켜고/끄고 위 성능 실험을 진행해 보았다.

결론: kubedns가 켜져 있든 꺼져 있든 결과에는 무관하다.

실험 2. docker0 트래픽 분석

이제 부하를 준 상태에서 docker0의 트래픽을 분석하기로 한다. 두번의 실험을 하는데, 한번은 kube-proxy가 만든 iptables(kube-proxy iptables라고 이름짓자.) 기반으로 하고, 또 한번은 수작업으로 만든 iptables(manual DNAT iptables라고 이름짓자.) 기반으로 하여 패킷 dump를 한다.

# tcpdump -n -w dump.bin -i docker0 host 10.100.13.3

여기서 상이한 점을 발견할 수 있었다.

  • kube-proxy iptables

    16:26:45.785852 IP ares-k29.60862 > 10.100.13.3.domain: 3020+ AAAA?  cdn.megafile.co.kr. (36)
    16:26:45.785869 IP ares-k29.36495 > 10.100.13.3.domain: 3765+ A? www.google.com. (32)
    16:26:45.785871 IP ares-k29.51732 > 10.100.13.3.domain: 64486+ A? upushgw.uplus.co.kr. (37)
    16:26:45.785889 IP ares-k29.54114 > 10.100.13.3.domain: 58931+ A? urs.microsoft.com. (35)
    16:26:45.786002 IP 10.100.13.3.domain > 122.32.52.130.59329: 3765 16/4/4 A 210.92.119.24, A 210.92.119.30, A 210.92.119.40, A 210.92.119.39, A 210.92.119.49, A 210.92.119.25, A 210.92.119.44, A 210.92.119.54, A 210.92.119.45, A 210.92.119.20, A 210.92.119.59, A 210.92.119.34, A 210.92.119.50, A 210.92.119.55, A 210.92.119.35, A 210.92.119.29 (424)
    16:26:45.786077 IP 10.100.13.3.domain > 49.168.157.153.54114: 58931 2/4/4 CNAME urs.microsoft.com.nsatc.net., A 40.74.131.199 (235)
    16:26:45.786158 IP 10.100.13.3.59718 > 43.255.253.254.domain: 53404 AAAA? megagrid.stm.flexcdn.kr. (41)
    16:26:45.786158 IP 10.100.13.3.56159 > ns2.bora.net.domain: 61786 [1au] A?  upushgw.uplus.co.kr. (48)
    16:26:45.786612 IP ns2.bora.net.domain > 10.100.13.3.56159: 61786*- 1/2/3 A 106.103.255.208 (143)
    16:26:45.786733 IP 10.100.13.3.domain > 49.166.115.71.51732: 64486 1/2/2 A 106.103.255.208 (132)
    
  • manual DNAT iptables tcpdump 결과

    17:42:46.733732 IP 182.225.81.191.53632 > 10.100.13.3.domain: 22076+ A? search.naver.com. (34)
    17:42:46.733769 IP 125.191.163.58.50421 > 10.100.13.3.domain: 42077+ [1au] ANY? learnengs.com. (42)
    17:42:46.733835 IP 112.153.78.17.45538 > 10.100.13.3.domain: 62723+ A? m.nate.com. (28)
    17:42:46.733847 IP 182.215.65.22.64472 > 10.100.13.3.domain: 58869+ A? spcdnpc.i-mobile.co.jp. (40)
    17:42:46.733944 IP 10.100.13.3.domain > 182.225.81.191.53632: 22076 3/3/3 CNAME search.naver.com.nheos.com., A 125.209.230.167, A 43.250.153.7 (205)
    17:42:46.734003 IP ns-227.awsdns-28.com.domain > 10.100.13.3.58021: 42710*- 1/4/1 CNAME dmhleyyccs922.cloudfront.net. (220)
    17:42:46.734026 IP 182.216.81.166.23063 > 10.100.13.3.domain: 62097+ A? logins.daum.net. (33)
    17:42:46.734018 IP 10.100.13.3.domain > 112.153.78.17.45538: 62723 1/2/2 A 211.115.10.31 (112)
    17:42:46.734028 IP 125.176.202.64.1028 > 10.100.13.3.domain: 36728+ A? uict2.ez-i.co.kr. (34)
    17:42:46.734188 IP 10.100.13.3.domain > 125.176.202.64.1028: 36728 NXDomain 0/1/0 (92)
    17:42:46.734206 IP 10.100.13.3.domain > 182.216.81.166.23063: 62097 3/2/2 CNAME logins.g.daum.net., A 180.70.134.231, A 180.70.134.230 (154)
    

차이가 보이는가? kube-proxy iptables는 DNS query packet의 source ip가 항상 ares-k29이다. 즉, container가 구동 중인 호스트의 source ip로 변경된 것을 볼 수 있다.

manual DNAT iptables는 DNS query packet의 source ip가 실제 source ip로 유지된다.

여기서 얻은 정보는

kube-proxy iptables는 들어오는 트래픽에 대해서 SNAT를 한다.

는 것이다.

물론 BIND container로부터 나가는 트래픽도 SNAT을 한다.

kube-proxy iptables를 보면, 들어오는 packet에 0x4 mark를 하고, 이 mark가 있는 패킷은 SNAT를 한다.

다음과 같이 요약하자. * kube-proxy iptables

- incoming dns query: SNAT -> DNAT
- outgoing dns query/response: SNAT

outgoing dns query는 BIND container가 수행하는 recursive query를 의미한다.

  • manual DNAT iptables
    • incoming dns query: DNAT only
    • outgoing dns query/response: SNAT

위 두가지 경우의 차이점은 결국 incoming dns query에서 kube-proxy는 SNAT을 한다는 점이다. 이것이 성능 차이를 유발할까?

SNAT의 성능 문제

SNAT의 성능은 conntrack table과도 관련이 있다. netfilter는 SNAT을 위해 conntrack table를 이용한다. conntrack table은 memory에 만들어져 SNAT의 정보를 저장하기 위해 사용된다. 그렇다면, 혹시 conntrack table이 full이 되어 문제가 발생하는 것일까? 이것을 검증하기 위해 트래픽을 흘릴 때 conntrack table의 사용 개수를 저장해 보았다. conntract table의 사용개수는 /proc/sys/net/netfilter/nf_conntrack_count로 볼 수 있다.

아래는 약 2500 rps 트래픽이 흐를 때 conntrack 개수 로그이다.

2017. 05. 19. () 13:52:34 KST :  33 / 4194304
2017. 05. 19. () 13:52:35 KST :  33 / 4194304
2017. 05. 19. () 13:52:36 KST :  149 / 4194304
2017. 05. 19. () 13:52:39 KST :  147 / 4194304
2017. 05. 19. () 13:52:40 KST :  266 / 4194304
2017. 05. 19. () 13:52:44 KST :  390 / 4194304
2017. 05. 19. () 13:52:45 KST :  466 / 4194304
2017. 05. 19. () 13:52:50 KST :  666 / 4194304
2017. 05. 19. () 13:53:09 KST :  1448 / 4194304
2017. 05. 19. () 13:53:13 KST :  1535 / 4194304
2017. 05. 19. () 13:53:20 KST :  1538 / 4194304
2017. 05. 19. () 13:53:24 KST :  1534 / 4194304
2017. 05. 19. () 13:53:25 KST :  1534 / 4194304
2017. 05. 19. () 13:53:36 KST :  12151 / 4194304
2017. 05. 19. () 13:53:39 KST :  19715 / 4194304
2017. 05. 19. () 13:53:40 KST :  21730 / 4194304
2017. 05. 19. () 13:53:48 KST :  37222 / 4194304
2017. 05. 19. () 13:53:49 KST :  38920 / 4194304
2017. 05. 19. () 13:53:50 KST :  40596 / 4194304
2017. 05. 19. () 13:53:53 KST :  45767 / 4194304
2017. 05. 19. () 13:53:55 KST :  47409 / 4194304
2017. 05. 19. () 13:53:57 KST :  50770 / 4194304
2017. 05. 19. () 13:53:58 KST :  52357 / 4194304
2017. 05. 19. () 13:53:59 KST :  53992 / 4194304
2017. 05. 19. () 13:54:00 KST :  55560 / 4194304
2017. 05. 19. () 13:54:01 KST :  57140 / 4194304
2017. 05. 19. () 13:54:02 KST :  58643 / 4194304
2017. 05. 19. () 13:54:03 KST :  60146 / 4194304
2017. 05. 19. () 13:54:08 KST :  55721 / 4194304
2017. 05. 19. () 13:54:09 KST :  54887 / 4194304
2017. 05. 19. () 13:54:10 KST :  54108 / 4194304
2017. 05. 19. () 13:54:11 KST :  53541 / 4194304
2017. 05. 19. () 13:54:12 KST :  53018 / 4194304
2017. 05. 19. () 13:54:15 KST :  51364 / 4194304
2017. 05. 19. () 13:54:21 KST :  49690 / 4194304
2017. 05. 19. () 13:54:27 KST :  48690 / 4194304
2017. 05. 19. () 13:54:30 KST :  48029 / 4194304

위 포맷은 : / 이다.

최대 가능 수자는 4M 개인데 60k개 정도에서 최고점을 찍고 하강한다. 실제로 그 시점 쯤에 health check가 실패한다고 한다.

conntrack table의 최대 개수와 무관하게 SNAT의 정보 저장에 한계가 있다.

생각해 보면 당연하다. SNAT의 bucket entry는 (소스 IP, 소스 포트, 목적 IP, 목적 포트)와 같이 unique tuple들로 정의된다. 이 4개의 element가 하나라도 다르면 다른 entry가 된다.

예) unique한 SNAT entry 1.1.1.1:50135 -> 10.100.13.3:53 1.1.1.1:50136 -> 10.100.13.3:53 1.2.1.1:50137 -> 10.100.13.3:53 …

kube-proxy iptables의 문제는 incoming traffic을 모두 SNAT하기 때문에 소스 IP가 같다는 것이다. 그리고 bind container는 한 개이기 때문에 목적 IP와 목적 port도 동일하다. 그렇다면, unique하기 위해서는 소스 포트를 다르게 할 수 밖에 없다. 예) 1.1.1.1:50135 -> 10.100.13.3:53 1.1.1.1:50136 -> 10.100.13.3:53 1.1.1.1:50137 -> 10.100.13.3:53

여기서 포트에 대해 잠시 고찰해 보자.

TCP/UDP 포트는 16bit unsigned로 되어 0 ~ 65535 범위에서 할당할 수 있다.

전체 할당 가능한 port 수가 이론적으로는 65536개이나 이미 사용 중인 포트를 제외 하면, ephemeral port의 개수는 60k 정도가 될 것이다.

kube-proxy iptables가 SNAT을 하면, 60k 정도의 dns 세션 처리가 가능하다. 만약 그 이상의 query가 들어오면, SNAT 소스 포트 할당을 할 수 없게 된다.

결국 이 SNAT이 kubernetes 성능 저하의 원인이 된다.

질문과 대답

Q. 이전에 4개의 container를 띄워 분산하면 1개가 처리하는 것보다 rps가 더 잘 나왔는데, SNAT이 문제라면 이것은 어떻게 설명할 수 있는가?

A. 목적 IP가 분산되기 때문이다. SNAT의 기본 unique 단위는 (소스 IP, 소스 포트, 목적 IP, 목적 포트) tuple이다. 예) 1.1.1.1:50135 -> 10.100.13.3:53 1.1.1.1:50136 -> 10.100.13.3:53 1.1.1.1:50135 -> 10.100.13.4:53 1.1.1.1:50136 -> 10.100.13.4:53

그러므로 container 개수를 늘리면 어느 정도 성능 향상이 된다.

실제로 container 개수를 늘려 가면서 성능 시험을 하기도 했다. 20개까지 늘려 보았으나 10,000 - 12,000 rps정도에서 수렴한다. 그 이상 container를 늘려도 시스템 과부하로 성능이 나오지 않는다.

과연 한 물리 머신에 20개의 container를 띄워 서비스하는 것이 의미가 있을까?

결론

  • kubernetes의 external IP 서비스 기능으로 부하 부산을 하는 방식으로는 SNAT으로 인한 성능 저하때문에 고성능의 서비스를 하기에 어렵다.
  • 고성능 서비스를 위해서는 외부 Load Balancer를 사용하는 것이 좋다.

에필로그 : health check의 기준 문제

위에서 말한대로 health check는 100개의 query를 던져 100개 다 응답을 받으면 healthy하다고 간주한다. 의문점은 100개를 다 받는데 기다리는 시간(response wait time, RWT) 설정이 어떻게 되어 있는 가 하는 것이다. 고객에게 문의하였으나 이 설정값을 모른다고 한다. 이 RWT가 너무 짧다면 health check의 기준이 너무 높게 설정된 것이 아닌가 하는 것이다. 예를 들어, RWT를 1초로 했다면, bind container가 1.1초 후에 응답했더라도 실패가 된다는 것이다.

Appendix

  • kube-proxy iptables (참고: 외부 IP 정보는 x.x.x.x로 변환함)

    *nat
    :PREROUTING ACCEPT [2754:238511]
    :INPUT ACCEPT [3:195]
    :OUTPUT ACCEPT [6:360]
    :POSTROUTING ACCEPT [6:360]
    :DOCKER - [0:0]
    :KUBE-MARK-DROP - [0:0]
    :KUBE-MARK-MASQ - [0:0]
    :KUBE-NODEPORTS - [0:0]
    :KUBE-POSTROUTING - [0:0]
    :KUBE-SEP-4HNHNBOBUT2EV3H2 - [0:0]
    :KUBE-SEP-BCSN2HMZQJBRJOX4 - [0:0]
    :KUBE-SEP-UPD7ND2TPNSDBE6L - [0:0]
    :KUBE-SERVICES - [0:0]
    :KUBE-SVC-357LDXEIX7XFORLA - [0:0]
    :KUBE-SVC-NPX46M4PTMTKRN6Y - [0:0]
    :KUBE-SVC-QVE7WEGHBCUIDG2Z - [0:0]
    -A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
    -A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
    -A OUTPUT -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
    -A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
    -A POSTROUTING -s 10.100.13.0/24 ! -o docker0 -j MASQUERADE
    -A POSTROUTING -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING
    -A DOCKER -i docker0 -j RETURN
    -A KUBE-MARK-DROP -j MARK --set-xmark 0x8000/0x8000
    -A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000
    -A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -m mark --mark 0x4000/0x4000 -j MASQUERADE
    -A KUBE-SEP-4HNHNBOBUT2EV3H2 -s 10.100.13.3/32 -m comment --comment "default/ares-k29-bindapp:dns-tcp-53" -j KUBE-MARK-MASQ
    -A KUBE-SEP-4HNHNBOBUT2EV3H2 -p tcp -m comment --comment "default/ares-k29-bindapp:dns-tcp-53" -m tcp -j DNAT --to-destination 10.100.13.3:53
    -A KUBE-SEP-BCSN2HMZQJBRJOX4 -s 10.100.13.3/32 -m comment --comment "default/ares-k29-bindapp:dns-udp-53" -j KUBE-MARK-MASQ
    -A KUBE-SEP-BCSN2HMZQJBRJOX4 -p udp -m comment --comment "default/ares-k29-bindapp:dns-udp-53" -m udp -j DNAT --to-destination 10.100.13.3:53
    -A KUBE-SERVICES ! -s 10.24.0.0/16 -d 10.24.0.1/32 -p tcp -m comment --comment "default/kubernetes:https cluster IP" -m tcp --dport 443 -j KUBE-MARK-MASQ
    -A KUBE-SERVICES -d 10.24.0.1/32 -p tcp -m comment --comment "default/kubernetes:https cluster IP" -m tcp --dport 443 -j KUBE-SVC-NPX46M4PTMTKRN6Y
    -A KUBE-SERVICES ! -s 10.24.0.0/16 -d 10.24.138.29/32 -p udp -m comment --comment "default/ares-k29-bindapp:dns-udp-53 cluster IP" -m udp --dport 53 -j KUBE-MARK-MASQ
    -A KUBE-SERVICES -d 10.24.138.29/32 -p udp -m comment --comment "default/ares-k29-bindapp:dns-udp-53 cluster IP" -m udp --dport 53 -j KUBE-SVC-357LDXEIX7XFORLA
    -A KUBE-SERVICES -d 10.5.6.29/32 -p udp -m comment --comment "default/ares-k29-bindapp:dns-udp-53 external IP" -m udp --dport 53 -j KUBE-MARK-MASQ
    -A KUBE-SERVICES -d 10.5.6.29/32 -p udp -m comment --comment "default/ares-k29-bindapp:dns-udp-53 external IP" -m udp --dport 53 -m physdev ! --physdev-is-in -m addrtype ! --src-type LOCAL -j KUBE-SVC-357LDXEIX7XFORLA
    -A KUBE-SERVICES -d 10.5.6.29/32 -p udp -m comment --comment "default/ares-k29-bindapp:dns-udp-53 external IP" -m udp --dport 53 -m addrtype --dst-type LOCAL -j KUBE-SVC-357LDXEIX7XFORLA
    -A KUBE-SERVICES -d x.x.x.x/32 -p udp -m comment --comment "default/ares-k29-bindapp:dns-udp-53 external IP" -m udp --dport 53 -j KUBE-MARK-MASQ
    -A KUBE-SERVICES -d x.x.x.x/32 -p udp -m comment --comment "default/ares-k29-bindapp:dns-udp-53 external IP" -m udp --dport 53 -m physdev ! --physdev-is-in -m addrtype ! --src-type LOCAL -j KUBE-SVC-357LDXEIX7XFORLA
    -A KUBE-SERVICES -d x.x.x.x/32 -p udp -m comment --comment "default/ares-k29-bindapp:dns-udp-53 external IP" -m udp --dport 53 -m addrtype --dst-type LOCAL -j KUBE-SVC-357LDXEIX7XFORLA
    -A KUBE-SERVICES ! -s 10.24.0.0/16 -d 10.24.138.29/32 -p tcp -m comment --comment "default/ares-k29-bindapp:dns-tcp-53 cluster IP" -m tcp --dport 53 -j KUBE-MARK-MASQ
    -A KUBE-SERVICES -d 10.24.138.29/32 -p tcp -m comment --comment "default/ares-k29-bindapp:dns-tcp-53 cluster IP" -m tcp --dport 53 -j KUBE-SVC-QVE7WEGHBCUIDG2Z
    -A KUBE-SERVICES -d 10.5.6.29/32 -p tcp -m comment --comment "default/ares-k29-bindapp:dns-tcp-53 external IP" -m tcp --dport 53 -j KUBE-MARK-MASQ
    -A KUBE-SERVICES -d 10.5.6.29/32 -p tcp -m comment --comment "default/ares-k29-bindapp:dns-tcp-53 external IP" -m tcp --dport 53 -m physdev ! --physdev-is-in -m addrtype ! --src-type LOCAL -j KUBE-SVC-QVE7WEGHBCUIDG2Z
    -A KUBE-SERVICES -d 10.5.6.29/32 -p tcp -m comment --comment "default/ares-k29-bindapp:dns-tcp-53 external IP" -m tcp --dport 53 -m addrtype --dst-type LOCAL -j KUBE-SVC-QVE7WEGHBCUIDG2Z
    -A KUBE-SERVICES -d x.x.x.x/32 -p tcp -m comment --comment "default/ares-k29-bindapp:dns-tcp-53 external IP" -m tcp --dport 53 -j KUBE-MARK-MASQ
    -A KUBE-SERVICES -d x.x.x.x/32 -p tcp -m comment --comment "default/ares-k29-bindapp:dns-tcp-53 external IP" -m tcp --dport 53 -m physdev ! --physdev-is-in -m addrtype ! --src-type LOCAL -j KUBE-SVC-QVE7WEGHBCUIDG2Z
    -A KUBE-SERVICES -d x.x.x.x/32 -p tcp -m comment --comment "default/ares-k29-bindapp:dns-tcp-53 external IP" -m tcp --dport 53 -m addrtype --dst-type LOCAL -j KUBE-SVC-QVE7WEGHBCUIDG2Z
    -A KUBE-SERVICES -m comment --comment "kubernetes service nodeports; NOTE: this must be the last rule in this chain" -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS
    -A KUBE-SVC-357LDXEIX7XFORLA -m comment --comment "default/ares-k29-bindapp:dns-udp-53" -j KUBE-SEP-BCSN2HMZQJBRJOX4
    -A KUBE-SVC-NPX46M4PTMTKRN6Y -m comment --comment "default/kubernetes:https" -m recent --rcheck --seconds 10800 --reap --name KUBE-SEP-UPD7ND2TPNSDBE6L --mask 255.255.255.255 --rsource -j KUBE-SEP-UPD7ND2TPNSDBE6L
    -A KUBE-SVC-NPX46M4PTMTKRN6Y -m comment --comment "default/kubernetes:https" -j KUBE-SEP-UPD7ND2TPNSDBE6L
    -A KUBE-SVC-QVE7WEGHBCUIDG2Z -m comment --comment "default/ares-k29-bindapp:dns-tcp-53" -j KUBE-SEP-4HNHNBOBUT2EV3H2
    COMMIT
    # Completed on Wed May 17 17:08:09 2017
    # Generated by iptables-save v1.4.21 on Wed May 17 17:08:09 2017
    *filter
    :INPUT ACCEPT [186:70129]
    :FORWARD ACCEPT [15052:982087]
    :OUTPUT ACCEPT [177:15601]
    :DOCKER - [0:0]
    :DOCKER-ISOLATION - [0:0]
    :KUBE-FIREWALL - [0:0]
    :KUBE-SERVICES - [0:0]
    -A INPUT -j KUBE-FIREWALL
    -A FORWARD -j DOCKER-ISOLATION
    -A FORWARD -o docker0 -j DOCKER
    -A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
    -A FORWARD -i docker0 ! -o docker0 -j ACCEPT
    -A FORWARD -i docker0 -o docker0 -j ACCEPT
    -A OUTPUT -j KUBE-FIREWALL
    -A OUTPUT -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
    -A DOCKER-ISOLATION -j RETURN
    -A KUBE-FIREWALL -m comment --comment "kubernetes firewall for dropping marked packets" -m mark --mark 0x8000/0x8000 -j DROP
    COMMIT
    
  • manual DNAT iptables

    *nat
    :PREROUTING ACCEPT [2754:238511]
    -A PREROUTING -i eth3 -p udp -m udp --dport 53 -m state --state NEW -j DNAT --to-destination 10.100.13.3:53
    -A PREROUTING -i eth3:1 -p udp -m udp --dport 53 -m state --state NEW -j DNAT --to-destination 10.100.13.3:53
    :INPUT ACCEPT [3:195]
    :OUTPUT ACCEPT [6:360]
    :POSTROUTING ACCEPT [6:360]
    -A POSTROUTING -s 10.100.13.0/24 ! -o docker0 -j MASQUERADE
    :DOCKER - [0:0]
    :KUBE-MARK-DROP - [0:0]
    :KUBE-MARK-MASQ - [0:0]
    :KUBE-NODEPORTS - [0:0]
    :KUBE-POSTROUTING - [0:0]
    :KUBE-SEP-4HNHNBOBUT2EV3H2 - [0:0]
    :KUBE-SEP-BCSN2HMZQJBRJOX4 - [0:0]
    :KUBE-SEP-UPD7ND2TPNSDBE6L - [0:0]
    :KUBE-SERVICES - [0:0]
    :KUBE-SVC-357LDXEIX7XFORLA - [0:0]
    :KUBE-SVC-NPX46M4PTMTKRN6Y - [0:0]
    :KUBE-SVC-QVE7WEGHBCUIDG2Z - [0:0]
    COMMIT
    # Completed on Wed May 17 17:08:09 2017
    # Generated by iptables-save v1.4.21 on Wed May 17 17:08:09 2017
    *filter
    :INPUT ACCEPT [186:70129]
    :FORWARD ACCEPT [15052:982087]
    :OUTPUT ACCEPT [177:15601]
    :DOCKER - [0:0]
    :DOCKER-ISOLATION - [0:0]
    :KUBE-FIREWALL - [0:0]
    :KUBE-SERVICES - [0:0]
    COMMIT
    
]]>
Sat, 20 May 2017 00:00:00 +0900
http://www.iorchard.net/2017/05/17/ssh_with_otp_authentication.html http://www.iorchard.net/2017/05/17/ssh_with_otp_authentication.html <![CDATA[Google Authenticator(OTP)를 이용한 SSH 2차 인증]]> Google Authenticator(OTP)를 이용한 SSH 2차 인증

개요

일반적으로 SSH 인증 방벙들 중 사용자로부터 패스워드를 입력받는 방법인 password authentication을 많이 사용한다. 가장 심플하며 사용하기도 쉽고, 간편한 방법이다. 하지만 보안상 안전한 방법은 아니다. 패스워드가 간단한 규칙이거나 복잡하지 않을수록 brute force attack(무작위 대입 공격)에 취약해진다. 또 패스워드를 직접 입력해야만 하는 방식인 만큼, 이른바 shoulder surfing 등의 사용자 부주의로 인한 패스워드 노출에도 신경 써야 할것이다.

SSH key authentication은 보다 안전한 방법이다. client의 허락된 ssh public key를 server에 미리 넣어둠으로 패스워드 입력없이 서버에 접근 할수 있다. 사용자 입력이 없으니 key logger나 위에서 언급한 shouler surfing 등에서 자유롭고, 사용자는 패스워드를 기억할 필요도 없으니 어딘가에 적어둘 필요도 없어 노출될 가능성도 매우 적다. 그러나 이 방법 역시 단점이 없는것은 아니다. 많은 서버들로 구성된 대규모 환경에서 사용자들의 public key(authorized_keys) 관리는 예상보다 훨씬 간단하지 않다. 또한 어떻게든 사용자의 private key가 유출된다면 아무 의미없는 인증이 되어 버린다.

보다 나은 여러 방법이 있겠지만, 이 문서는 Google Authenticator(https://github.com/google/google-authenticator)를 이용하여 OTP(one-time password) 2차 인증에 대해 기술할 것이다. 위의 인증방법과 더불어 하나의 인증이 추가되는 2차 인증(Two-Factor Authentication: 2FA)이니만큼, 보안적으론 더 안전할수밖에 없다. 그러나 언제나 그렇듯 보안과 편의는 반비례 관계이다. 로그인할때마다 OTP를 확인하고 입력하는 추가작업은 생각보다 불편할 것이다. 하지만 비교적 높은 수준의 보안을 간단하게 구성할 수 있으니, 여러 인증방법중 하나로 충분히 고려해볼만 할것이다.

OTP

One Time Password. 말그대로 일회성 비밀번호이다. 아마 대부분은 온라인 뱅킹등에서 이미 경험했을것이라 추측된다. 일반적으로 인증에 사용되는 값들은 고정되어 있어 유출될 가능성을 항상 염두해야한다. 하지만 OTP의 경우 그 값이 동적으로 바뀌게되므로 유출시도 안전한 방법으로 볼수 있다.

여러 OTP 중에서 google authenticator를 선택했다. 일단 Opensource Project(Apache License 2)이고, 현재 가장 널리 사용중인 OTP이다. 대부분의 모바일 OS(ios, android, windows등)에서 모두 지원하고, 서버 패키지 역시 대부분의 OS(debian, redhat)가 지원했다. 다른 대체가능한 것으로는 FreeOTP(https://freeotp.github.io/)가 있고, 상용제품으로 authy(https://authy.com/)가 많이 사용되는듯 보인다.

설치

위에서 언급했던대로 debian의 공식 repository에 packages가 등록되어있다. (jessie이상) 따라서 debian, ubuntu등에서는 아래의 명령으로 쉽게 설치가 가능하다.

# apt-get install libpam-google-authenticator

pam모듈과 함께 간단한 cli둘도 설치가 된다. rhel계열이라면 아래의 package명이다.

# yum install google-authenticator

google-authenticator 설정

google-authenticator는 OS 사용자마다 다른 설정을 할수 있다. 설정에 대한 정보는 $HOME/.google-authenticator 파일로 저장되며, google-authenicator라는 명령으로 생성할수 있다.

$ google-authenticator

google-authenticator를 실행하면 설정에 대한 몇가지 질문이 interactive하게 진행된다.

Do you want authentication tokens to be time-based (y/n)

google-authenticator는 2가지 인증 방법을 지원한다. TOTP(time-based)와 HOTP(HMAC-based; Hash-based Message Authentication Code)이다. 이는 인증에 대한 알고리즘인데 시간을 기반으로 할것인지, Event(google-authenticator는 counter를 쓴다)를 기반으로 할것인지를 선택한다.

이 OTP인증에서 서버와 클라이언트(핸드폰의 OTP앱 등)간에 서로 어떠한 정보도 주고 받지 않는다. 동일한 인자값(input)으로 동일한 알고리즘을 사용하여, 결과값인 token이 서로 같은지/다른지만 판단하여 인증을 처리한다. TOTP의 경우 Secret Key와 함께 시간을 인자값으로 사용한다. 동일한 secret key와 시간이라면 같은 token의 결과가 나오게 되고, 이 인증이 통과되는것이다. 따라서 TOTP는 ntp등의 시간 동기화가 매우 중요하다. 시간이 다를 경우 token이 달라지므로 인증이 실패하게 된다. (물론 이 시간차를 대비한 설정도 아래에서 진행한다.)

HOTP는 secret key와 함께 counter를 사용하여 인증한다. 이 counter는 1부터 시작하며 인증이 성공하거나 클라이언트의 증가 요청(Reload 버튼)있을 경우 1씩 증가한다. 따라서 30초마다 token이 변경되는 TOTP와 다르게, HOTP는 EVENT(요청)가 없다면 그 token이 변경되지 않는다. 그래서 HOTP보단 TOTP가 보안상 더 안전하다고 한다.

Your new secret key is: IQM4TUAPUVLGUFM7
Your verification code is 424471
Your emergency scratch codes are:
  68300423
  73586279
  29748570
  20970148
  42616217

그럼 바로 위와 같은 형태로 몇개의 Key들이 생성되고 표시된다. (더불어 QR Code가 Console상에 뿌려지는데 클라이언트에서 이 QR Code를 찍게되면 자동으로 설정이 진행된다.) secret key는 시간과 함께 알고리즘의 인자값으로 사용될 중요한 key다. 노출을 조심하도록 하자. 뒤이어 나온 verification code는 token값이다. otp라면 30초마다 한번씩 변경될것이다. 마지막으로 emergency scratch code 5개가 출력되는데 이것은 말그대로 emergency에 사용된다. 시간과 상관없이 위 code를 입력하면 인증이 성공된다. 한번 인증이 진행된 code는 더이상 사용될수 없게 지워진다.

Do you want me to update your "$HOME/.google_authenticator" file (y/n)

설정 파일을 업데이트 할지를 결정한다. n를 선택한다면 바로 종료된다.

Do you want to disallow multiple uses of the same authentication
token? This restricts you to one login about every 30s, but it increases
your chances to notice or even prevent man-in-the-middle attacks (y/n)

30초마다 생성된 동일한 token의 중복 인증 허용여부를 설정한다. 즉 한번 인증이 성공된다면 동일한 token으로는 인증이 실패하게된다. 30초가 지나 새로운 token이 생성되어야만 인증이 성공하게 된다. 당연히 yes로 진행해야 보안상 유리할것이다.

By default, tokens are good for 30 seconds and in order to compensate for
possible time-skew between the client and the server, we allow an extra
token before and after the current time. If you experience problems with poor
time synchronization, you can increase the window from its default
size of 1:30min to about 4min. Do you want to do so (y/n)

TOTP의 경우 서버와 클라이언트의 시간이 틀어지게되면 인증이 실패하게 된다. 이를 대비하여 현재시간 앞뒤로 여유시간을 둬서 유효기간을 늘릴수 있다.

OTP 인증에서 window size라는 값으로 설정할수 있는데, default window size는 3이다. 이 여유 폭을 늘려 4분 차이까지 유효 인증을 처리할지, 즉 17로 변경하겠느냐는 설정이다. TOTP에서 window size 1은 30초이다. 현재시간을 기점으로 앞뒤로 “8(앞) + 1(현재) + 8(뒤)”의 여유 인증시간, 4분이 된다. 반면 HOTP는 window size 1는 count 1이 된다. 서버는 마지막으로 인증이 성공한 counter를 저장하고 그 이후 window size만큼 counter를 증가하면서 token값이 일치 유무를 확인하게 된다. 이 window size가 큰 만큼 보안상 좋을리는 없다.

If the computer that you are logging into isn't hardened against brute-force
login attempts, you can enable rate-limiting for the authentication module.
By default, this limits attackers to no more than 3 login attempts every 30s.
Do you want to enable rate-limiting (y/n) y

무작위 대입 공격(brute-foce)를 대비하여 30초동안 3번 이상의 로그인 시도를 제한할수 있다. 물론 enable하는것이 보안상 유리하다고 할 수 있겠다.

위 질문들이 모두 끝나면 ~/.google_authenticator 파일이 생성되어 있을것이다. 해당 파일의 permission은 400이다. 열어보면 위에서 생성된 secret와 설정들이 모두 저장됨을 확인할수 있을것이다.

IQM4TUAPUVLGUFM7
" RATE_LIMIT 3 30
" WINDOW_SIZE 17
" DISALLOW_REUSE
" TOTP_AUTH
68300423
73586279
29748570
20970148
42616217

첫번째 라인이 Secret key이고 뒤이어 “로 시작되는 OTP의 설정이 있다. 각 설정의 의미는 위에서 언급해 모두 추측할수 있을것이다. 마지막 5라인은 emergency scratch codes이다. 코드가 사용되면 설정파일에서 해당 코드가 삭제된다.

만약 HOTP로 진행했다면 설정이 약간 다를것이다.

" RATE_LIMIT 3 30
" WINDOW_SIZE 17
" HOTP_COUNTER 1

TOTP_AUTH 대신 HOTP_COUNTER가 설정되어 있음을 알수 있다. 초기값인 1이 기록되어 있고, 이후엔 마지막으로 인증성공에 사용된 counter가 기록될것이다.

위 과정을 진행하면서 눈치챌 수도 있었겠지만, TOTP는 위 설정파일을 여러 서버에 적용할수 있다. 서버 시간이 동기화되어 있다면 해당 파일을 복사하여, 하나의 client 설정으로 여러 서버들의 인증을 진행할 수 있을것이다.

pam 설정

사용하고자하는 서비스에 google_authenticator pam module을 추가한다. 우리는 ssh에 적용할것이므로 /etc/pam.d/sshd에 아래 라인을 추가하면 된다. 원한다면 ssh가 아닌 su, sudo, passwd등과 같은 명령에도 opt를 적용할수 있다.

# vi /etc/pam.d/sshd
auth required pam_google_authenticator.so

이 pam_google_authenticator이 위에서 생성한 ~/.google-authenticator를 읽어들여 인증을 진행한다. 만약 해당 파일이 없다면 당연히 인증은 실패하게 된다. pam_google_authenticator의 man page를 보면 ssh에 적용시 sshd_config에 ChallengeResponseAuthentication yes를 추가하라고 나와있다. 해당 설정이 없을 경우 default는 yes지만, 배포판의 기본 설정은 다를수 있다. 확인하고 추가하도록 하자.

# vi /etc/ssh/sshd_config
ChallengeResponseAuthentication yes

변경했다면 당연히 sshd를 restart 해야할것이다.

# systemctl restart sshd

이후 ssh로 서버에 접근하면 아래와 같이 패스워드 인증과 함께 2차 OTP 인증을 진행하게 될것이다.

# ssh 192.168.68.100 -luser
Password:
Verification code:

Client 설치

mobile os의 app market에서 “google opt”로 검색하면 client 앱을 찾을수 있을것이다. OTP 서버 추가도 간편하다. 위 설정을 진행하면서 출력된 QR CODE를 찍어도 되고, 아니면 직접 secret key를 입력하면 된다. 등록시 사용자명을 입력받는데 이는 OTP생성시 진행한 os 계정명일 필요는 없다. 구분를 위한 사용자 편의일뿐 인증에는 사용되지 않는다.

설정이 완료되면 해당 OTP에 token이 표시될것이다. TOTP라면 30초마다 리셋될것이고, HOTP라면 Count 증가를 위한 버튼이 나타난다. 이제 표시된 token으로 인증을 진행하면 된다.

]]>
Wed, 17 May 2017 00:00:00 +0900
http://www.iorchard.net/2017/04/28/kubernetes_bind_dns_성능_시험_계속되는_문제.html http://www.iorchard.net/2017/04/28/kubernetes_bind_dns_성능_시험_계속되는_문제.html <![CDATA[kubernetes BIND DNS 성능 시험 - 계속되는 문제]]> kubernetes BIND DNS 성능 시험 - 계속되는 문제

안양에서 이현우 부장님과 함께 BIND 컨테이너 성능 시험을 했다. PORCH은 어느 정도 마무리가 되었는데(솔라리스용 BIND 앱 배포도 완료함), KUBIC은 아직 실전 투입을 하지 못하고 있다.

계속된 문제

BIND 컨테이너에 실제 트래픽에 투입하면, probing하는 쪽에서 100% 응답이 오지 않는 문제로 인해 실전에 투입하지 못하고 있는 상황이다. 컨테이너 하나가 받을 수 있는 적정 requests per second(rps)가 1000 정도 인 것으로 파악되었다. 이 정도의 수치로는 KUBIC을 상용에 적용할 의미가 없다.

실험 내용

오늘 나는 두가지 실험을 하였다. 두 실험 모두 kube-proxy를 끄고(끄지 않으면, kube-proxy가 계속 iptables를 건드리므로) 수동으로 iptables 규칙을 추가하여 성능을 보는 실험이다.

  1. kube-proxy를 끄고 수동으로 iptables 규칙 추가 (statistic 모듈의 nth 모드 사용)

    1. KUBIC으로 ares-k29 minion에 4개의 BIND container 구동

    2. iptables 규칙 청소 후 kube-proxy 종료:

      kube-proxy –cleanup-iptables

    3. kube-proxy가 만든 iptables 규칙 삭제 (iptables -F && iptables -F -t nat)

    4. iptables 규칙 추가:

      -A PREROUTING -i eth3 -p udp -m udp --dport 53 -m state --state NEW -m statistic --mode nth --every 4 --packet 0 -j DNAT --to-destination 10.100.13.2:53
      -A PREROUTING -i eth3 -p udp -m udp --dport 53 -m state --state NEW -m statistic --mode nth --every 3 --packet 0 -j DNAT --to-destination 10.100.13.3:53
      -A PREROUTING -i eth3 -p udp -m udp --dport 53 -m state --state NEW -m statistic --mode nth --every 2 --packet 0 -j DNAT --to-destination 10.100.13.4:53
      -A PREROUTING -i eth3 -p udp -m udp --dport 53 -m state --state NEW -m statistic --mode nth --every 1 --packet 0 -j DNAT --to-destination 10.100.13.5:53
      -A PREROUTING -i eth3:1 -p udp -m udp --dport 53 -m state --state NEW -m statistic --mode nth --every 4 --packet 0 -j DNAT --to-destination 10.100.13.2:53
      -A PREROUTING -i eth3:1 -p udp -m udp --dport 53 -m state --state NEW -m statistic --mode nth --every 3 --packet 0 -j DNAT --to-destination 10.100.13.3:53
      -A PREROUTING -i eth3:1 -p udp -m udp --dport 53 -m state --state NEW -m statistic --mode nth --every 2 --packet 0 -j DNAT --to-destination 10.100.13.4:53
      -A PREROUTING -i eth3:1 -p udp -m udp --dport 53 -m state --state NEW -m statistic --mode nth --every 1 --packet 0 -j DNAT --to-destination 10.100.13.5:53
      
    5. 실전 투입 (가중치를 높여가며, probing이 100% 완벽한 지 실험)

    6. 성능 문제 없음 확인

  2. kube-proxy를 끄고 수동으로 iptables 규칙 추가 (statistic 모듈의 random 모드 사용)

    1. KUBIC으로 ares-k29 머신에 4개의 BIND container 구동

    2. kube-proxy 종료 (systemctl stop kube-proxy, kube-proxy를 종료해도 컨테이너에는 영향을 주지 않는다.)

    3. kube-proxy가 만든 iptables 규칙 삭제 (iptables -F && iptables -F -t nat)

    4. iptables 규칙 추가:

      -A PREROUTING -i eth3 -p udp -m udp --dport 53 -m state --state NEW -m statistic --mode random --probability 0.25000000000 -j DNAT --to-destination 10.100.13.2:53
      -A PREROUTING -i eth3 -p udp -m udp --dport 53 -m state --state NEW -m statistic --mode random --probability 0.33332999982 -j DNAT --to-destination 10.100.13.3:53
      -A PREROUTING -i eth3 -p udp -m udp --dport 53 -m state --state NEW -m statistic --mode random --probability 0.50000000000 -j DNAT --to-destination 10.100.13.4:53
      -A PREROUTING -i eth3 -p udp -m udp --dport 53 -m state --state NEW -j DNAT --to-destination 10.100.13.5:53
      -A PREROUTING -i eth3:1 -p udp -m udp --dport 53 -m state --state NEW -m statistic --mode random --probability 0.25000000000 -j DNAT --to-destination 10.100.13.2:53
      -A PREROUTING -i eth3:1 -p udp -m udp --dport 53 -m state --state NEW -m statistic --mode random --probability 0.33332999982 -j DNAT --to-destination 10.100.13.3:53
      -A PREROUTING -i eth3:1 -p udp -m udp --dport 53 -m state --state NEW -m statistic --mode random --probability 0.50000000000 -j DNAT --to-destination 10.100.13.4:53
      -A PREROUTING -i eth3:1 -p udp -m udp --dport 53 -m state --state NEW -j DNAT --to-destination 10.100.13.5:53
      
    5. 실전 투입 (가중치를 높여가며, probing이 100% 완벽한 지 실험)

    6. 성능 문제 없음 확인

실험 결과

위 실험으로 다음과 같은 결론이 나온다.

  1. kubernetes가 만든 컨테이너에서도 iptables statistic 확장 모듈의 nth/random 모드 수동 적용 시 성능에 전혀 문제가 없음이 확인되었다.
  2. 위 1번 결과로 인해 성능 문제는 flannel 문제가 아닌 것으로 판명되었다. flannel문제라면 위 실험에서도 성능에 문제가 발생해야 한다. (위 실험에서 서비스를 중지한 것은 kube-proxy 뿐이다.)

그러나 kube-proxy가 만드는 iptables 규칙 적용 시에는 여전히 성능 문제가 발생하므로 남은 과제는 kube-proxy가 만드는 iptables 규칙에 어떤 문제가 있는 지 파악이 필요하다.

]]>
Fri, 28 Apr 2017 00:00:00 +0900