쿠버네티스(Kubernetes) 의 서비스(Service) 에 타입에는 Loadbalancer, NodePort 등을 지원한다. 문제는 Loadbalancer 는 클라우드(Cloud) 서비스 사업자를 위한 것으로 AWS, GCP, Azure 에서 제공하는 Loadbalancer 를 위한 것이다.
그런데, 많은 쿠버네티스의 사례를 살펴보면 Loadbalancer 를 사용한 사례가 아주 많다. 그래서 굳이 클라우드 서비스 사업자가 아니라고 하더라도 Loadbalancer 를 사용할 수 있도록 해보자해서 만들어진게 바로 Metallb 이다.
필자는 KVM 리눅스 가상화에 쿠버네티스를 설치했다. KVM 가상 시스템의 게스트 OS 들은 브릿지 네트워크 모드로 이기 때문에 필자의 공유기에서 자동으로 아이피를 할당 받거나, 공유기의 대역에 IP를 고정으로 사용하기도 한다. 필자의 공유기는 192.168.96.0/20 대역폭을 사용하도록 세팅을 해놨기 때문에 할당 가능한 IP는 차고도 넘친다.
Metalb 는 이렇게 외부에서 접속이 가능한 아이피 대역을 할당해 사용한다.
설치
설치를 위해서 필요한게 있는데, CNI 가 먼저 있어야 한다. Flannel, Calico 와 같은 CNI 가 설치가 먼저 되어 있어야 한다. 설치는 설치를 위한 메니페스토 파일을 이용한다.
아주 유용한 것중에 하나지만 종종 잘못 이해하고 잘못 설정하게되는 NGINX 기능이 속도 제한(rate limiting) 이다. 이것은 주어진 특정한 시간동안 HTTP 요청양을 제한할 수 있도록 한다. 하나의 요청은 웹사이트의 홈페이지를 위한 GET 요청이나 로그인 폼에 POST 요청이다.
속도 제한은 보안 목적을 위해서 사용되어 질 수 있는데, 에를들어 부르트 포스 패스워드 추정 공격의 속도를 낮출 수 있다. 이것은 실제 사용자에 대해 들어오는 요청 비율을 일반적인 값으로 제한하고 (로깅을 통해) 대상 URL을 식별함으로써 DDos 공격으로부터 보호하는데 도움이 될 수 있다. 더 일반적으로, 동시에 아주 많은 사용자 요청으로 인해 압도되어지는 업스트림 애플리케이션 서버를 보호하는데 사용되어진다.
이 블로그에서는 NGINX의 속도 제한의 기본사항과 더블어 고급 설정에 대해서 다룬다. 속도 제한은 NGINX Plus 에 같은 방법으로 동작한다.
NGINX 속도 제한은 어떻게 작동하나
NGINX 속도 제한은 leaky bucket algorithm 을 사용하는데, 이것은 대역폭이 제한된 상황에서 버스트를 다루기 위해 패킷 교환 컴퓨터 네트워크와 통신에 널리 사용된다. 비유를 하자면, 물이 상단에서 부어지고 바닥에서 물이 줄줄 세는 양동이와 같다. 물을 붓는 속도가 양동이에서 새는 속도를 초과하면 물통이 넘치게 된다. 이것을 요청처리로 다시 보면, 물은 클라이언트로부터 요청으로 볼수 있고 양동이는 FIFO 스케줄링 알고리즘에 따라 처리를 기다리는 요청에 대한 큐로 볼 수 있다. 새는 물은 서버에 의해서 처리하기 위한 버퍼에 존재하는 요청으로 볼수 있고 물통이 넘치는 것은 폐기되고 결코 서비스될 수 없는 요청으로 볼 수 있다.
기본적인 속도 제한 설정하기
속도 제한은 두개의 메인 지시문으로 설정되는데, 다음의 예제처럼 limit_req_zone 과 limit_req 이다.
limit_req_zone 은 속도 제한에 대한 파라메터를 정의하고 limit_req 는 나타나는 컨텍스트 내에(예를들어, /login/ 에 대한 모든 요청) 속도 제한을 활성화 한다.
limit_req_zone 지시문은 일반적으로 http 블럭에 정의하는데, 여러 컨텍스트에서 사용할 수 있다. 이것은 다음과 같이 세개의 파라메터를 가진다.
Key – 제한이 적용되는 요청 특징을 정의한다. 위 예에서 NGINX 변수 $binary_remote_addr 은 클라이언트 IP 주소의 이진 표현을 보유한다. 이것은 각각의 유니크한 IP 주소를 세걔의 파라메터로 정의한 요청 속도로 제한 한다. (우리가 이 변수를 사용하는 이유는 일반 문자열로 표현되는 클라이언트 IP 주소 $remote_addr 변수보다 적은 공간을 가지기 때문이다.)
Zone – 각 IP 주소에 상태를 저장하는 사용되는 공유 메모리 존과 어떻게 요청이 제한된 URL 에 접근하는지에 대해 정의한다. 공유 메모리에 정보를 유지하는 다는 것은 NGINX 워크 프로세스들 사이에 공유될 수 있다는 뜻이다. 이 정의는 두 파트를 가진다: 존 이름은 zone= 키워드로 구별되어지고 크기는 콜론(:) 뒤에 온다. 약 16,000 IP 주소에 대한 상태정보는 1 메가 크기를 가지며 위 예제에서는 약 160,000 주소를 저장할 수 있다. 만약 NGINX 가 새로운 엔트리(IP 주소) 를 추가할 필요가 있을때 스토리지가 꽉 찼다면, 가장 오래된 엔트리를 제거 한다. 여유 공간이 새로운 레코드를 수용하기에 충분하지 않는다면, NGINX 는 503 (Service Temporarily Unavailable) 코드 상태를 리턴 한다. 게다가, 메모리 고갈을 방지하기 위해서는 NGINX 가 새로운 엔트리를 만들때마다 이전 60초 동안 사용되지 않은 항목을 최대 2개까지 제거해야 한다.
Rate – 최대 요청 속도 지정. 위 예제에서, 속도는 초당 10 요청을 초과할 수 없다. NGINX 는 실제로 요청을 밀리초(millisecond) 초 단위로 추적하므로 이 제한은 100 밀리초(ms) 마다 1개 요청에 해당 한다. 이 예제에서 버스트를 허용하지 않았기 때문에 이전에 허용 된 요청 이후 100ms 미만에 도착하면 요청이 거부 된다.
limit_req_zone 지시문은 속도 제한과 공유 메모리 존에 대한 파라메터를 정의하지만 실제로 요청 속도를 제한하지 않는다. 요청 속도를 제한하기 위해서는 특정한 location 이나 server 블럭에 limit_req 지시문을 포함하는 제한 적용이 필요하다. 위 예제에서는 /login/ 에 대한 요청 속도를 제한하고 있다.
그래서 이제 각각의 유니크한 IP 주소는 /login/ 에 대해 초당 10 요청으로 제한되고, 좀 더 정확하게, 이전 요청의 100ms 안에 /login/ URL 에 대한 요청을 할 수 없다.
버스트 핸들링
위 예제에서, 서로 100ms 이내에 2개의 요청을 받으면 어떻게 되나? 두번째 요청에 대해 NGINX 는 클라이언트에 503 상태 코드를 리턴 한다. 아마도 이것은 우리가 원하는게 아닐 수도 있는데, 애플리케이션은 실제로는 버스티(bursty, 폭발적인) 경향이 있다. 대신 우리는 초과 요청을 버퍼하길 원하고 적시에 서비스를 원한다. 여기서 업데이트 된 구성에서와 같이 limit_req 에 burst 파라메터를 사용 하다.
burst 파라메터 사용
Apache
1
2
3
4
5
location/login/{
limit_reqzone=mylimitburst=20;
proxy_passhttp://my_upstream;
}
burst 파라메터는 얼마나 많은 요청을 존에서 지정한 속도를 초과해 클라이언트가 만들 수 있는지를 정의한다. (위 예제 mylimit 존에서, 속도 제한은 초당 10 요청 혹은 100ms 당 1개 다) 이전요청 이후에 100ms 보다 조금 빠르게 도착한 요청은 쿠에 넣는데 여기서 우리는 큐 크기를 20으로 지정했다.
만약 21 요청이 주어진 IP 주소로 동시에 도착한다면, NGINX 은 첫번째를 즉각 upstream 서버 그룹에 포워드하고 나머지는 20개는 대기열에 넣는다. 그런 다음 100ms 마다 큐된 요청을 포워드하고 들어오는 요청이 큐된 요청 개수가 20개가 넘어가게 될때에만 클라이언트에 503 을 리턴 한다.
No Delay 를 가지는 큐
burst 를 가지는 설정은 트래픽 흐름을 부드럽게 만들지만, 사이트가 느려져 보일 수 있기 때문에 그렇게 실용적이 않다. 우리의 예제에서, 큐에 20번째 패킷은 포워드되기 위해 2초를 기다리는데 이 시점에서 이에 대한 응답은 클라이언트에게 더 이상 유용하지 않을 수 있다. 이 상황을 해결하기 위해서는 burst 파라메터와 함께 nodelay 파라메터를 추가해야 한다.
nodely 파라메터를 가지는 burst 설정
Apache
1
2
3
4
5
location/login/{
limit_reqzone=mylimitburst=20nodelay;
proxy_passhttp://my_upstream;
}
nodelay 파라메터를 사용하면, NGINX 는 여전히 burst 파라메터에 따라 큐에 슬랏을 할당하고 설정된 속도 제한 부여하지만, 큐된 요청의 포워딩 간격을 두지 않는다. 대신, 요청이 아주 순간에 들어오면, NGINX 는 큐에 슬롯이 활용할 수 있는 한 즉시 포워드 된다. 이것은 “taken” 으로 슬롯을 표시하고(mark) 적절한 시간이 경과 할 때까지 다른 요청에 의해서 사용하도록 해제하지 않는다. (위 예제에서, 100ms 지난후)
이전과 마찬가지로, 20 슬롯 큐가 비어있고 21 요청이 주어진 IP 주소로부터 동시에 들어온다고 가정해 보자. NGINX 는 모든 21번째 요청을 즉각 포워드 하고 큐에 20 번 슬롯을 “taken” 으로 표시하고 나면 100ms 마다 1개 슬롯을 해제하게 된다. (만약 25개의 요청이 있다면, NGINX 는 그들중에 21번째를 즉각 포워드 하고 20번 슬롯을 “taken” 으로 표시하고 나머지 4개의 요청은 503 상태로 거절한다.)
이제 첫번째 요청 세트가 포워드된 이후 101ms 에 또 다른 20개의 요청이 동시에 도착한다고 생각해보자. 큐에서 오직 1 슬롯만이 해제된 상태다. 그래서 NGINX 는 1개 요청을 포워드 하고 다른 19개의 요청은 503 상태로 거절 한다. 대신 20개의 새로운 요청 전에 501ms 가 지났다면 5 개의 슬롯이 해제 된 상태이고 NGINX 는 즉각 5개의 요청을 포워드 하고 15개를 거절 한다.
이런 효과는 초당 10개의 속도 제한과 동일하다. nodelay 옵션은 요청들 사이에 허용되는 시간 간격을 제한하지 않고 속도 제한을 적용할경우에 유용하다.
Note: 대부분, 우리는 limit_req 지시문에 burst 와 nodelay 파라메터를 포함하길 권장한다.
두단계 속도 제한
NGINX Plus R17 이나 NGINX 오픈 소스 1.15.7 에서, 전형적인 웹 브라우저 요청 패턴을 수용하기 위해 요청 버스트를 허용하도록 NGINX 를 설정할 수 있고, 추가적인 과도한 요청을 최대한 지점까지 제한하며 그 이상으로 추가적인 과도한 요청은 거부 된다. 두 단계 속도 제한은 limit_req 지시문에 delay 파라메터로 활성화 된다.
두 단계 속도 제한을 설명하기 위해, 여기서는 초당 5개 요청으로 속도를 제한함으로써 웹 사이트를 보호하도록 NGINX 를 설정한다. 웹사이트는 일반적으로 한 페이지당 4-6 리소스를 가지며 12 리소를 넘지 않는다. 이 설정은 최대 12 요청을 허용 하며 첫 8 요청은 지연(delay) 없이 처리 된다. 지연(delay) 는 5 r/s 한도를 적용하기 위해 8개 과도한 요청 이후에 추가 된다. 12개 과도한 요청 이후에, 모든 추가된 요청은 거부된다.
delay 파라메터는 정의된 속도 제한을 준수하기 위해 버스트 크기 내에서 과도한 요청을 조절(delayed) 하는 지점을 정의한다. 이 구성을 사용하면, 8r/s 에서 지속적인 요청 스트림을 만드는 클라이언트는 다음과 같은 동작을 경험하게 된다.
첫 8 요청은 (delay 값) delay 없이 NGINX Plus 에 의해서 프록시 된다. 그 다음 4개 요청은 (burst – delay) 정의된 5 r/s 속도를 넘지 않도록 지연(delay) 된다. 그 다음 3개 요청은 거부되는데, 전체 버스트 크기를 넘어섰기 때문이다. 후속 요청은 지연된다.
고급 구성 예제
NGINX 의 다른 기능과 함께 기본적인 속도 제한 설정과 조합해, 아주 미묘하게 트래픽을 제한을 구현 할 수 있다.
Allowlisting
이 예제는 “allowlist” 에 없는 모든 요청에 대한 속도 제한을 어떻게 하는 보여준다. (역, allowlist 에 있는 IP 는 속도 제한을 하지 않는다.)
이 예제는 geo 와 map 지시문을 사용해 구성했다. geo 블럭은 allowlist 에 IP 주소에 대해서 $limit 변수에 0의 값을 할당했고 다른 모든 것에는 1 의 값을 할당 했다. 그리고 나서 우리는 그러한 값을 키(key) 로 바꾸기 위해서 map 사용하는데, 그것은:
만약 $limit 가 0 이면, $limit_key 는 빈 문자열(empty string) 이 설정 된다.
만약 $limit 가 1 이면, $limit_key 는 클라이언트의 IP 주소를 바이너리 포맷으로 설정 된다.
이 두가지를 조합해보면, $limit_key 는 허용된 IP 주소에 대해서는 빈 문자열로 설정되고, 그렇지 않으면 클라이언트 IP 주소가 설정 된다. limit_req_zone 지시문에 첫번째 파라메터가 빈 문자열이면, 제한이 적용되지 않는데, 허용된 IP 주소들 (10.0.0.0/8 과 192.168.0.0/24 서브넷) 은 제한이 걸리지 않는다. (역, 10.0.0.0/8 과 192.168.0.0/24 값은 0 이다) 다른 모든 IP 주소들은 초당 5 요청으로 제한이 걸린다.
limit_req 지시문은 제한을 / location 에 적용하고 지연 없는 포워딩으로 설정된 제한을 초과하여 최대 10패킷이 허용된다.
location 에 다중 limit_req 지시문 포함하기
하나의 location 에 다중 limit_req 지시문을 포함할 수 있다. 주어진 요청과 매칭되는 모든 제한은 적용되는데, meaing the most restrictive one is used.(가장 제한적인 것이 사용된다는 뜻이다.). 예를들어, 하나 이상의 지시문이 지연을 부과하는 경우 가장 긴 지연이 사용된다. 비슷하게, 설사 다른 지시분이 그것을 허용한다고 하더라도 어떤 지시문에 효과가 있다면 요청들은 거부 된다.
허용된 목록에 IP 주소들은 첫번째 속도 제한(req_zone) 에 걸리지 않지만 두번째(req_zone_wl) 에 걸리며 초당 15 요청으로 제한된다. 허용목록에 없는 IP 주소드른 양쪽 모두 속도 제한에 걸리는데, 훨씬 제한적인 설정인 초당 5 요청이 적용 된다.
연관 기능 설정하기
Logging
기본적으로, NGINX 는 속도 제한으로 지연되거나 드랍된 요청을 기록한다. 다음과 같다.
Nginx Logging
Apache
1
2015/06/1304:20:00[error]120315#0: *32086 limiting requests, excess: 1.000 by zone "mylimit", client: 192.168.1.2, server: nginx.com, request: "GET / HTTP/1.0", host: "nginx.com"
로그 엔트리에 포함된 필드는 다음과 같다.
2015/06/1304:20:00 – 로그 엔트리에 기록된 날짜와 시간
[error] – 심각 수준
120315#0 – NGINX 워커의 프로세스 ID 와 쓰레드 ID 인데 # 로 구분 된다.
*32086 – 속도가 제한된 프록시된 연결 ID
limiting requests – 로그 엔트리 기록이 속도 제한(rate limit) 라는 지시자
excess –
zone – 부과 된 속도 제한을 정의한 zone
client – 요청을 생성한 클라이언 IP 주소
server – 서버의 IP 주소 혹은 호스트이름
request – 클라이언트 의해서 생성한 실제 HTTP 요청
host – HTTP 헤더의 Host 값
기본적으로, 위 예제에서 [error] 를 본것처럼 NGINX 는 error 수준에서 거부된 요청들이 기록된다. (지연된 요청들은 한단계 낮은 수준으로, 기본적으로 warn, 기록된다. 로깅 수준을 바꾸기 위해, limit_req_log_level 지시문을 사용해라. 여기서, 우리는 거부된 요청은 warn 수준에서 기록되도록 지정했다.
warn 수준의 로깅 설정
Apache
1
2
3
4
5
6
location/login/{
limit_reqzone=mylimitburst=20nodelay;
limit_req_log_levelwarn;
proxy_passhttp://my_upstream;
}
클라이언트에 Error 코드 전송하기
기본적으로 NGINX 는 클라이언트가 속도 제한을 초과했을때에 503 (Service Temporarily Unavailable) 상태 코드를 응답한다. 다른 상태 코드를 지정하고 싶다면 limit_req_status 지시문을 사용해라.
limit_req_status 를 사용해 다른 상태코드 지정하기
Apache
1
2
3
4
location/login/{
limit_reqzone=mylimitburst=20nodelay;
limit_req_status444;
}
특정 Location 에 모든 요청 거부하기
만약 특정 URL 에 대한 모든 요청들을 거부하기 원한다면, 단지 제한을 하는 말고, deny all 지시문을 포함한 location 블럭을 설정한다.
특정 Location 에 모든 요청 거부
Apache
1
2
3
location/foo.php{
denyall;
}
결론
우리는 HTTP 요청에 대해 서로 다른 location 에 요청 속도를 지정하고 burst와 nodelay 파라메터와 같은 속도 제한에 대한 추가적인 기능 설정등의 NGINX 와 NGINX Plus 가 제공하는 속도 제한에 많은 기능을 알아봤다. 우리는 또한 거부된 요청과 지연된 요청을 어떻게 기록하는지 설명했고, 허용되거나 거부된 클라이언트 IP 주소들에 대해 서로 다른 제한을 적용하는 등의 고급 설정에 대해서도 알아 봤다.
오래전에 “시작하세요! 엘라스틱서치” 라는 책을 구매해 본적이 있다. 지금도 소장하고 있는데, 엘라스틱서치에 대한 기본적인 개념을 익히는데 손색이 없는 책이라 평가하고 싶다. 단, 내가 볼때에 엘라스틱서치에 버전이 6.4 이다 보니 없어져버린 기능들은, 예를들어 펫싯, 볼 필요가 없었다.
오늘은 최신의 ElasticSearch 7.13 버전에서 이 책에 내용을 한번 살펴보고자 한다. 엘라스틱서치는 버전이 올라감에 따라 변경된 부분이 상당히 많아진다는 점을 생각해보면 꽤 도움이 될법한 내용들이라 생각한다.
엘라스틱서치의 데이터 구조 – 타입(Type) 없어짐
엘라스틱서치의 데이터 구조를 설명에서 인덱스(Index), 타입(Type), 도큐먼트(Document) 단위로 이루어진다고 설명했다. 하지만 엘라스틱서치 7.x 로 버전이 높아짐에 따라 타입은 이제 없다. 이에 대해서 엘라스틱서치 개발사에서 다음과 같은 페이지를 제공해 설명해 주고 있다.
systemd 는 이제 리눅스 시스템의 뼈대가 되는 기본운영 방법이 되었다. 기존에는 System V Init 이였지만 RHEL 7, Ubuntu 16.04부터 기본 시스템운영 프로그램이 되었다.
systemd 는 ‘ctl’ 로 끝나는 명령어들의 집합으로 제어가 가능하다. systemctl, journalctl, timedatectl, hostnamectl, loginctl 이 대표적이다. systemctl 의 경우에는 systemd unit 파일들에 대한 제어와 설정이 가능하다. systemd unit 파일은 기존의 Systemv V init Script 를 대체하는 것으로 일종의 시스템 데몬 프로그램들이라고 보면 된다. 시스템이 시작될때에 자동으로 시작되게 한다거나 종료하게 한다거나 하는것들을 가능하게 한다.
unit 파일은 텍스트 파일이기 때문에 언제든지 파일을 열어서 편한대로 편집이 가능한데, systemctl 명령어는 이런 unit 파일에 대한 편집기능을 제공 한다. unit 파일이 있는 곳으로 가서 텍스트 편집기를 열 필요가 없이 systemctl edit –full 명령어를 이용하면 가능하다.
문제는 systemctl edit — full 명령어를 입력했을 때에 실행되는 에디터가 nano 로 열리는 경우가 많은데, 이것을 바꿀 수 있다.
제일 쉬운 방법은 SYSTEMD_EDITOR 환경변수 설정이다. 대부분 Bash 를 사용하기 때문에 이것을 다음과 같이 쉘 환경변수로 설정 해준다.
SYSTEMD_EDITOR 환경변수 설정
ZSH
1
export SYSTEMD_EDITOR=vim
매번 로그인 할때마다 입력하기 귀찮으면 ~/.bashrc 파일에 추가해주면 로그인할때마다 자동 적용된다.
만일 일반계정으로 로그인을 하고 sudo systemctl edit –full 명령어를 이용한다면 /etc/sudoers 파일에 다음을 추가해 줘야 한다.
/etc/sudoers 파일 추가
ZSH
1
2
sudo visudo
Defaults env_keep+="SYSTEMD_EDITOR"
sudo 를 실행하는 계정에 있는 환경 변수인 SYSTEMD_EDITOR 를 유지하라는 뜻이다.
update-alternatives 로 기본 에디터 변경
update-alternatives 는 alternative 가 가능한 여러 명령어들을 바꿔 주는 기능을 한다. 이것은 매우 유용한데, 예를들어 Python3 관련 버전이 여려개일 경우에 python3 에 기본 버전을 시스템적으로 지정해 줄 수 있다. update-alternatives –list 명령어를 입력하면 현재 등록된 대체가능한 명령어들이 보인다.
리눅스에서 편집기는 editor 라는 명령어로 되어 있고, 정확하게는 심볼릭 링크다, 이것을 update-alternatives 명령어로 바꿔주면 된다.
쿠버네티스 설치는 kubeadm 명령어를 이용하면 손쉽게 자동으로 이루어 진다. HA 를 위한 설치에서는 좀 더 손이 더 가겠지만 어쨌거나 그것도 kubeadm 을 이용한다. 쿠버네티스 설치에 많은 시간을 들이는 것이 그렇게 현명해 보이지는 않을 수 있지만 쿠버네티스의 기본적인 구조를 이해하는데 수동 설치만큼 유용한 것도 없다.
인터넷을 검색해보면 쿠버네티스 수동 설치는 보통 ‘Hard way’ 로 많이 나온다. ‘힘든 방법’ 등으로 번역할 수 있는데, 하나하나 수동으로 설치를 하게 된다. 이 글이 어렵다면 ‘Kubernetes Hard way’ 로 검색하면 많은 자료를 얻을 수 있다.
Requirements
쿠버네티스 수동 설치의 최종적인 모습은 다음과 같다.
위 그림은 ‘고가용성 토폴로지 선택‘ 문서에 나온 것이다. 컨트롤 플레인 노드는 적어도 3개, 워커 노드도 적어도 3개 그리고 중간에 로드 밸런서(Load balancer) 가 필요하다. 정리를 하면 다음과 같다.
컨트롤 플레인 노드(Control plane Node): 3개
워커 노드(Worker Node): 3개
로드 밸런서(Load balancer): 1개
이를 위해서 필자는 KVM 으로 VM 호스트를 6개를 만들었고 로드 밸런서는 KVM 호스트 컴퓨터에 HAProxy 를 구성하는 것으로 결정 했다.
툴 설치
쿠버네티스는 내부에 많은 프로그램들로 구성된다. 이들 프로그램들 간의 통신은 보안 통신을 기반으로 하는데, 이를 위해서는 TLS 인증서가 필요하다. 생성하는 방법은 다양하지만 CloudFlare 에서 만든 cfssl, cfssljson 명령어를 가장 많이 이용한다.
앞에서 쿠버네티스 컨트롤 플레인, 워커, 로드 밸런서에 대해서 언급을 했었는데 자세하게는 다음과 같다.
호스트 이름
도메인
아이피
kmaster1
kmaster1.systemv.local
192.168.96.46
kmaster2
kmaster2.systemv.local
192.168.96.47
kmaster3
kmaster3.systemv.local
192.168.96.48
haproxy
haproxy.systemv.local
192.168.96.7
kworker1
kworker1.systemv.local
192.168.96.49
kworker2
kworker2.systemv.local
192.168.96.50
kworker3
kworker3.systemv.local
192.168.96.51
도메인은 내부 도메인네임서버를 운영하고 있어 이를 이용했다. 운영체제는 우분투(Ubuntu) 20.04 LTS 다. KVM 의 네트워크는 NAT 가 아니라 브릿지(Bridge) 네트워크로 구성해 KVM 게스트를 외부에서도 자유롭게 접속되도록 구성 했다. KVM의 게스트는 공유기 IP 대역에서 고정IP로 설정을 했다.
TLS 인증서 생성하기
CA(Certificate Authority) 작성하기
쿠버네티스에서 사용하는 인증서는 Self Sign 인증서다. 보통 인증서라고 하면 HTTPS 통신을 위한 인증서를 많이 들어봤을 것이다. 이 인증서를 발급받기 위해서는 여러 과정이 필요한데, 이 과정을 자세히 알고 있다면 이 과정이 이해하기 쉽다.
HTTPS 인증서를 발급 받기 위해서는 CSR 을 먼저 작성해야 한다. 하지만 그 전에 필요한 것이 CA 다. 보통은 CSR 을 작성해 CA 기관에 제출하는 형태로 진행이되지만 Self Sign 할때에는 CA 가 없다. 공인 CA에서 Self Sign 인증서를 발급해줄리도 만무하고… 그래서 CA 를 먼저 생성해 준다.
Root CA 의 역할은 제출받은 CSR 를 받아서 개인키를 돌려준다. 보통 HTTP 인증서를 발급받을때에 Root CA 는 기관에서 하기 때문에 돈을 주고 해달라고 요청을 하게 된다. 하지만 Self Sign 인증서를 작성할때에는 Root CA 기관의 CA 키가 있다고 가정하고 그 CA Key + CSR 를 돌려서 개인키를 발급받게 된다. 요약하자면 공인 Root CA 에서 사용하는 CA 키를 셀프로 만드는 것이다.
O, 그러니까 조직(Oganization) 이 “system:masters” 라는 사실에 주목해야 한다.
이렇게하면 admin-key.pem, admin.pem 이 생성된다.
Kubelet 클라이언트 인증서
Kubelet 은 쿠버네티스 클러스터에 각 노드에 설치되는 에이전트다. 이는 파드(Pod) 에 컨테이너가 실행되도록 해준다.
쿠버네티스는 노드 인증(Node Authorizer) 라 부르는, 특히 Kubelets 에 의한 API 요청 인증에 특별한 목적의 인증 모드를 사용한다. 노드 인증(Node Authorizer) 로 인증되기 위해, Kubelets 는 반드시 system:node:<nodename> 의 이름을 가진 system:nodes 그룹에 그들이 있는 것처럼 확인된 자격증명을 사용해야 한다.
따라서 nodename 이 반드시 있어야 한다. 만일 DNS 서버가 없다고 해도 괜찮다. 쿠버네티스 클러스터를 위한 서버들에 /etc/hosts 에 정적으로 도메인을 할당하면 되며, 호스트도 마찬가지로 정적으로 넣어줘도 된다.
앞에서 워커 노드의 경우에 worker1, worker2, worker3, 3개의 worker 노드가 있다. 이에 대해서 CSR 을 만들어 준다.
Kubelet 클라이언트 인증서를 위한 CSR 요청서
ZSH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
$DOMAIN="systemv.local"
$forinstance inkworker1 kworker2 kworker3;do
cat>${instance}-csr.json<<EOF
{
"CN":"system:node:${instance}.${DOMAIN}",
"key":{
"algo":"rsa",
"size":2048
},
"names":[
{
"C":"US",
"L":"Portland",
"O":"system:nodes",
"OU":"Kubernetes The Hard Way",
"ST":"Oregon"
}
]
}
EOF
done
$cat kworker1-csr.json
{
"CN":"system:node:kworker1",
"key":{
"algo":"rsa",
"size":2048
},
"names":[
{
"C":"US",
"L":"Portland",
"O":"system:nodes",
"OU":"Kubernetes The Hard Way",
"ST":"Oregon"
}
]
}
CSR 은 인증서를 요청하기 위한 문서라고 보면 된다. 여기에는 요청에 필요한 각종정보를 적게 되어 있는데, O 의 경우에 조직(Oganization) 을 뜻하며 CN 은 Company Name 이다. 이것을 Kubelets 에서는 system:nodes 조직, system:nodes:worker1.systemv.local 회사등으로 인식한다고 보면 된다. CN 에 node 이름을 도메인명으로 했다는 것을 잘 봐둬야 한다.
이제 인증서를 만들어야 하는데, Self Root CA 와 CSR 를 조합해서 만든다. 단, 여기서 주의해야 하는 것은 호스트네임(hostname) 을 줘야 한다. 호스트네임은 콤마(,) 를 구분자로 여러개를 줄 수 있는데 여기서는 호스트네임, 아이피를 주고 생성한다. worker 서버 3개에 대해서 각각 만들어 준다.
2021/04/1611:27:47[INFO]signedcertificate with serial number9063887800656623067709796741742546672753221216
-hostname 에 콤마(,) 를 이용해 도메인, 호스트네임, IP 주소를 적고 있는데 이것은 SAN 확장을 위한 것이다. 멀티도메인 인증서에서 주로 많이 나오는 이야기인데, SAN 은 하나의 SSL 인증서에 여러개의 도메인을 사용할 수 있도록 해주는 X509 인증서의 확장이다.
이렇게 해서 생성한 인증서는 다음과 같다.
Kubelet 클라이언트 인증서
1
2
3
4
5
6
kworker1-key.pem
kworker1.pem
kworker2-key.pem
kworker2.pem
kworker3-key.pem
kworker3.pem
-hostname 으로 준 도메인,호스트네임,IP 주소가 인증서에 잘 있는지를 확인하는 방법은 다음과 같다.
TLS 인증서는 CSR 과 RSA Public Key 조합으로 구성되며 이것을 Root CA 의 Private Key 를 이용해 암호화 된 상태다. 따라서 그냥 텍스트 에디터로 열어보면 암호화 스트링을 볼수 있는데, openssl 명령어를 이용하면 위와같이 텍스트로 확인이 가능하다. -hostname 으로 준 도메인,호스트네임,IP 주소는 DNS 와 IP Address 로 인식이 되었다.
Kubernetes API Server 인증서라고 설명되어 있지만 앞에 다이어그램을 보면, API Server 는 쿠버네티스 컨트롤 플레인 노드에 있게 된다. 그리고 로드 밸런서를 통해서 워커 노드와 통신을 하게 된다. 이뿐만 아니라 API Server 는 외부에 클라이언트와 Kubectl 을 통해서 통신도 해야 한다.
이를 위해서 인증서에는 쿠버네티스 컨트롤 플레인 서버들과 로드밸런서에 대해 각각 인증이 가능해야 한다.
-hostname 옵션에 보면 haproxy 의 도메인과 IP 주소를 연다라 입력해줬고, 그 이후에 kmaster1~3 까지 IP 주소를 입력해 줬다. 그리고 localhost 에 해당하는 127.0.0.1 을 입력해주고 10.32.0.1 을 입력해줬다. 이 주소는 후에 컨트롤 플레인을 만들때에 사용할 Cluster IP 주소 대역 10.32.0.0/16 에 첫번째 주소다.
쿠버네티스 API 서버는 내부 DNS 네임에 kubernetes 를 Cluster IP 대역의 첫번째 IP와 묶어서 저장해놓는다. 따라서 이 10.32.0.1 주소는 Cluster IP 대역과 연관이 돼 있다는 것을 알아야 한다.
Service Account Key Pair
Kubernetes Controller Manager는 managing service accounts 문서에 설명된대로 키 페어를 사용하여 서비스 계정 토큰을 생성하고 서명합니다.
controller manager, kubelet, kube-proxy, scheduler 클라이언트 그리고 admin 사용자를 위한 kubeconfig 파일을 생성해준다.
쿠버네티스 공인 IP 주소
kubeconfig 는 쿠버네티스 API Server 에 접속이 필요하다. 고가용성을 위해서 API Server 앞에 외부 로드 밸런서에 IP 주소를 할당해서 사용했다. 이것은 아키텍쳐 다이어그램에서 로드밸런서가 쿠버네티스의 공인 IP가 되며 여기는 HAProxy 의 IP 주소다.
Kubernetes 공인 IP
ZSH
1
$KUBERNETES_PUBLIC_ADDRESS=192.168.96.7
kubelet 쿠버네티스 설정 파일
Kubelets 를 위한 kubeconfig 파일을 생성할때, Kubelet의 노드 이름과 일치하는 클라이언트 인증서를 사용해야 한다. 이것은 쿠버네티스 Node Authorizer 로 인증할 수 있도록 해준다.
쿠버네티스트는 클러스터 상태, 애플리케이션 설정들, secret 등 다양한 데이터를 저장한다. 쿠버네티스는 클러스터 데이터를 암호화를 제공한다.
쿠버네티스 Secret 의 암호화를 위한 암호화 설정과 암호화 키 생성을 위한 설정을 작성해준다.
encryption-config.yaml 파일 작성
ZSH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ENCRYPTION_KEY=$(head-c32/dev/urandom|base64)
$cat>encryption-config.yaml<<EOF
kind:EncryptionConfig
apiVersion:v1
resources:
-resources:
-secrets
providers:
-aescbc:
keys:
-name:key1
secret:${ENCRYPTION_KEY}
-identity:{}
EOF
이것을 컨트롤 플레인에 배포 해 준다.
HAProxy 서버 설치 및 설정
HAProxy 는 컨트롤 플레인을 로드 밸런싱을 해주는 역할을 한다. 다이어그램을 보면 워커와 컨트롤 플레인을 연결해주는 것으로 나오는데, 워커가 컨트롤 플레인을 하나의 도메인으로 연결하기 위한 것이기도 하다.
컨트롤 플레인은 외부에 통신을 6443 포트를 이용하게 된다. 도메인, IP 가 다른 컨트롤 플레인을 하나의 도메인 haproxy.systemv.local:6443 로 묶게 된다.
설치는 Ubuntu 20.04 서버에 APT 명령어로 패키지 설치 했다. 설정은 다음과 같이 간단하게 해줬다.
haproxy 설정
ZSH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
$sudo vim/etc/haproxy/haproxy.cfg
listen stats
bind*:9000
stats enable
stats realm Haproxy\Statistics
stats uri/haproxy_stats
stats auth admin:password
stats refresh30
mode http
frontend k8s
bind*:6443
default_backend k8s
mode tcp
option tcplog
backend k8s
balance roundrobin
#balance source
mode tcp
option tcplog
option tcp-check
server kmaster1.systemv.local192.168.96.46:6443check
server kmaster2.systemv.local192.168.96.47:6443check
server kmaster3.systemv.local192.168.96.48:6443check
$sudo systemctl restart haproxy
etcd 클러스터 설치
etcd 는 key-value 로 데이터를 저장해주는 서버다. 쿠버네티스는 자체적인 데이터를 하나도 가지고 있지 않다. 전부다 외부에 데이터를 저장하는데 그것을 해주는 것이 etcd 다. 서버 한대만 가지고 데이터를 저장하는 것은 위험함으로 이것도 클러스터로 여러 서버를 하나로 묶는다.
etcd 는 클러스터내에 서버들과 kmaster, kworker 서버들과 통신을 해야 한다. 하지만 쿠버네티스는 TLS 통신을 기반으로 하기 때문에 인증서를 함께 설치해 줘야 한다. 모든 서버와 통신을 위한 인증서로 Root CA 인증서와 kubernetes 인증서를 설치해 준다.
INTERNAL IP 는 kmaster1 서버의 IP, ETCD_NAME 은 kmaster1 이며 각각의 kmaster1, kmater2, kmaster3 에 IP를 적어주면 된다. 각각 kmaster 에서 INTERNAL IP 와 ETC_NAME 을 바꿔가면서 해주면 된다.
모두 정상적으로 설치가 되었다면 다음과 같이 확인할 수 있다.
etcd 확인하기
ZSH
1
2
3
4
5
6
7
8
$sudo ETCDCTL_API=3/usr/local/bin/etcdctl member list\
컨트롤 플레인(Controll Plane) 혹은 마스터(Master) 노드는 쿠버네티스에 두뇌에 해당한다. 모든 명령과 연산은 모두 컨트롤 플레인에서 이루어진다. 클라이언트의 명령은 컨트롤 플레인의 api server 에서 받아서 워커 노드에 kubelet 이 실행하는 형태다. 데이터는 모두 etcd 에 저장된다.
컨트롤 플레인의 주요 구성요소는 다음과 같다.
kube-apiserver
kube-controller-manager
kube-scheduler
kubectl
다운로드 및 설치
바이너리 파일을 받아야 한다. github 에는 소스코드만 올라와 있어서 어디서 받을지 잠시 헷깔렸는데, dowloadkubernetes 에서 받을 수 있었다.
여기서 –service-cluster-ip-range 를 잘 봐야 한다. 이것은 쿠버네티스 내부의 네트워크 주소 범위에 속한다. 앞에서 kubernetes 의 인증서를 작성할때에 이 주소 범위의 맨 앞의 주소인 10.32.0.1 을 넣어줬었다. 반드시 인증서에 포함되는 주소를 적어줘야 한다.
–service-account-signing-key-file, –service-account-issuer, –service-account-api-audiences 은 새롭게 추가된 기능으로 자세한 사항을 확인해 볼 필요가 있다.
쿠버네티스 Controller Manager 설정
앞에 api server 설치를 위해서 인증서를 모두 복사를 해뒀다. 컨틀로러 매니저를 위해서 kubeconfig 파일이 필요하다. 이를 복사해 준다.
쿠버네티스는 kubectl 을 이용해 명령을 받는다. 그러면 api server 에서 받고, 이것을 워커 서버에 kubelet 이 실행시키는 구조다. 문제는 api server 가 워커 노드에 kubelet 에 액세스를 할려면 RBAC 권한이 필요하다는 데 있다. RBAC(Role-Based Access Control) 는 사용자 역할 기반 접근 제어 방법이다.
지금까지 kubectl 명령어를 사용할려면 admin.kubeconfig 파일이 있어야 했다. 이 설정파일은 –server=https://127.0.0.1:6443 으로 로컬 호스트 api server 를 지칭하도록 만들어 졌다. 만일 kubectl 명령어를 외부에서 사용할려면 어떻게 해야할까?
WebLogic 12c 사일런트(Silent) 설치에 대해서 다룹니다. 현재 WebLogic 은 12.2.1.4 버전이다.
WebLogic 12.2.1.4
WebLogic 은 자바 엔터프라이즈 서버(Java Enterprise Server) 이다. 자바를 필요로 하는데, 특징은 Oracle 자바 jdk8 이 반드시 필요하다. JEE 7 스펙을 만족한다. 최근에 Oracle Linux 8.x 에서 인증이 되어서 설치할 수 있게 되었다.
시스템 계정 생성
WebLogic 을 운영하기 위한 시스템 계정을 생성 한다.
oracle 시스템 계정 생성
ZSH
1
2
3
4
5
6
7
8
]# groupadd -g 1000 oinstall
]# useradd -u 1100 -g oinstall oracle
]# passwd oracle
oracle사용자의비밀번호변경중
새암호:
잘못된암호:암호는8개의문자보다짧습니다
새암호재입력:
passwd:모든인증토큰이성공적으로업데이트되었습니다.
디렉토리 생성
WebLogic 설치를 위한 디렉토리를 다음과 같이 생성해 준다.
WebLogic 설치를 위한 디렉토리 생성
ZSH
1
2
3
4
5
6
]# mkdir -p /u01/app/oracle/middleware
]# mkdir -p /u01/app/oracle/config/domains
]# mkdir -p /u01/app/oracle/config/applications
]# mkdir -p /u01/software
]# chown -R oracle:oinstall /u01
]# chmod -R 775 /u01/
oracle 계정의 Bash 환경변수 세팅
oracle 계정으로 운영할 WebLogic 을 위해서 환경변수를 다음과 같이 세팅해 준다.
oracle 계정의 WebLogic 환경변수 세팅
ZSH
1
2
3
4
5
6
7
8
]# vi .bash_profile
export MW_HOME=/u01/app/oracle/middleware
export WLS_HOME=$MW_HOME/wlserver
export WL_HOME=$WLS_HOME
# Set to the appropriate JAVA_HOME.
export JAVA_HOME=/u01/app/oracle/jdk1.8.0_291
export PATH=$JAVA_HOME/bin:$PATH
Oracle Java 설치
oracle 계정으로 다음과 같이 Oracle 자바를 설치해 준다. Oracle 자바 1.8 버전을 다운받아서 설치 했다.
WebLogic 사일런스 설치를 하기 위해서 Response File 가 필요한데, 다음과 같이 작성해 준다.
WebLogic Response File 작성
ZSH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
]$vi/u01/software/wls.rsp
[ENGINE]
Response File Version=1.0.0.0.0
[GENERIC]
ORACLE_HOME=/u01/app/oracle/middleware
INSTALL_TYPE=WebLogic Server
MYORACLESUPPORT_USERNAME=
MYORACLESUPPORT_PASSWORD=<secure value="">
DECLINE_SECURITY_UPDATES=true
SECURITY_UPDATES_VIA_MYORACLESUPPORT=false
PROXY_HOST=
PROXY_PORT=
PROXY_USER=
PROXY_PWD=<secure value="">
COLLECTOR_SUPPORTHUB_URL=
Inventory 파일 작성
인벤토리를 위한 파일을 작성해 준다.
WebLogic Inventory File 작성
ZSH
1
2
3
]$vi/u01/software/oraInst.loc
inventory_loc=/u01/app/oraInventory
inst_group=oinstall
다운로드 WebLogic
다운로드를 할때에 주의해야할게 있다. WebLogic 이 발전하면서 Fusion Middleware Infrastructure 패키지화가 되었다. 다운로드 홈페이지에 가보면 두가지 타입으로 제공하고 있는데, “Generic Installer for Oracle WebLogic Server and Oracle Coherence:” 이것을 다운받아야 한다.
Use'weblogic.version -verbose'toget subsystem information
Use'weblogic.utils.Versions'toget version information forall modules
도메인 생성하기
WebLogic 은 도메인이라는 개념이 존재한다. 도메인은 하나의 WebLogic 서버처럼 여겨지는데, Admin 과 Managed 도메인 두가지 타입으로 나뉜다.
먼저 Admin 도메인 서버는 Managed 도메인 서버를 총괄한다. Managed 도메인 서버가 자바 애플리케이션을 실행하는 실제 서버라고 보면 되며 이 Managed 도메인 서버는 Admin 도메인 서버에 등록되어야만 동작할 수 있다.
도메인 생성에는 python 스크립트를 이용한다. 정확하게는 WebLogic 의 스크립트를 이용한다고 보면 된다. 이것은 스크립트뿐만 아니라 WLS 쉘을 이용해서도 할 수 있다.
다음과 같이 스크립트를 작성해 준다.
Admin 도메인 서버 생성을 위한 스크립트
Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#Read Template
selectTemplate('Basic WebLogic Server Domain','12.2.1.3.0')
loadTemplates()
# Enter Admin Server
cd('/Servers/AdminServer')
cmo.setListenAddress('')
setOption('ServerStartMode','prod')
set('ListenPort',7001)
create('AdminServer','SSL')
cd('SSL/AdminServer')
set('Enabled','True')
set('ListenPort',7002)
cd('/')
cd('Security/base_domain/User/weblogic')
set('Name','weblogic')
cmo.setPassword('weblogic1')
setOption('ServerStartMode','prod')
setOption('OverwriteDomain','True')
writeDomain('/home/oracle/wls_domain/krbank_pay')
closeTemplate()
exit()
‘krbank_pay’ 가 도메인이 된다. selectTemplate 에 버전을 표시하고 있는데, 이는 $MW_HOME/wlserver/common/templates/wls/wls.jar 파일을 압축 해제하고 나온 파일중에 template-info.xml 을 보면 버전 정보가 나온다.
ServerStartMode 를 prod 로 하고 있으며, 아이디와 패스워드를 입력해주고 있다. writeDomain 으로 Admin 도메인을 어디에 생성할 것인지 디렉토리 정보를 넘겨주고 있다. 이렇게 작성을 다 했다면 다음의 명령어로 실행해 준다.
Admin 도메인 생성 스크립트 실행.
ZSH
1
2
3
4
5
6
7
8
9
10
11
12
]$cd$MW_HOME/wlserver/common/bin
]$wlst.sh/u01/software/create_admin_domain.py
WARNING:Thisisadeprecated script.Please invoke the wlst.shscript under oracle_common/common/bin.
Initializing WebLogic Scripting Tool(WLST)...
Welcome toWebLogic Server Administration Scripting Shell
Type help()forhelp on available commands
Exiting WebLogic Scripting Tool.
]$
정상적으로 실행이 되었다면 /home/oracle/wls_domain/admin 디렉토리에 관련 파일들이 존재하게 된다. 디렉토리를 잘 보면 startWebLogic.sh 파일이 존하고 이것을 실행하면 Admin 도메인 서버가 실행 되게 되는데, 중간에 Username 과 password 를 입력받도록 되어 있다.
Redmine 을 설치하기 위해서는 먼저 gem 을 설치해줘야 한다. gem 은 Ruby 에서 사용하는 플랫폼을 작성해주는 프로그램이다. 그런데, 여기서 또 생각해봐야 하는것이 이것을 이제 일반계정으로 만들것인지 아니면 전역적인 시스템에 설치할 것인지하는 것을 고려해야 한다.
나는 이것을 redmine 이라는 일반 계정을 생성해서 설치하기로 했다. ruby 는 전역적으로 설치했지만 redmine 은 계정을 생성해 시스템과 분리되도록 하였다.
gem 설치
이제 gem 을 설치해 준다. gem 은 Ruby 설치 디렉토리에 함께 설치 됨으로 root 계정으로 실행을 해준다.