728x90

근래에 제공되는 대부분의 서비스는 SSL 혹은 TLS 를 이용하여 보안된 채널로 통신이 수행되도록 하는 것이 일반적입니다. 하물며 개인이 만드는 토이 프로젝트부터 대학교에서 과제로 제출하는 웹 사이트도 HTTPS 를 사용하지 않는 경우를 찾아보기 힘듭니다. 네, 물론 이 와중에서 Non-secure 로 버티는 곳들도 <당연히> 있긴 합니다. 

예전에는 HTTPS 를 이용하기 위해 인증서를 구매해야 했고, 보통 년단위의 갱신을 하다보니 비용 부담이 있었습니다. 하지만, 3개월마다 주기적으로 인증서를 업데이트 받는 조건으로 도메인 단위 인증서 (Domain Validation) 를 무료로 발급해주는 렛츠인크립트 Let's Encrypt 의 대중화 이후, 인증서 구매 비용도 단지 핑계가 된지 오래입니다. 


하지만, 이런 인증서 구매와 별개로 서버측에 SSL 설정을 구성하고 적용하는 것은 생각보다 까다롭게 생각하는 경우가 많습니다. 보통 한번 설정해 두면 수정하지 않아서 보안 사고의 원인이 되기도 하고, 설정을 잘못하는 바람에 특정한 단말기들이 서비스에 접근하지 못하는 장애를 겪기도 합니다. 원체 그 파급력이 크다보니 <그리 어려운 일이 아님에도 불구하고> 인증서 교체에 대해서 전문가를 찾는 경우가 종종 생깁니다.

이러한 엔지니어들의 고충을 잘 알고 있는 곳이 모질라 재단이기 때문일까요? 모질라에서 제공하는 SSL 설정 생성기는 이 모든 두려움으로부터...는 아니겠지만, 기본적인 설정 실수로 장애가 발생하는 것을 막아줄 수 있는 여러분의 친구, 좋은 도구가 될 수 있을 것 같습니다. 

다양하다! 

일단 이 도구가 커버해주는 서버측 소프트웨어의 범주가 무척 넓습니다. 널리 사용되는 아파치나 nginx 에서부터 AWS 의 ALB, ELB 도 포함이 되어 있습니다. HAProxy 와 Go 기반으로 서버측 엔드포인트를 만들때 쓸 수 있는 옵션들도 제공하고 있어 왠만한 서버측 도구의 SSL 설정에 활용도가 높아 보입니다. 

가운데 위치한 Mozilla Configuration 은 SSL/TLS 의 어떤 버전을 지원할 것인가에 따라 선택 가능한 항목입니다. 이 항목의 변경은 설정 생성시에 추가되는 Cipher Suite 의 종류에 영향을 주는 부분입니다. SSL/TLS 버전이 상위로 갈 수록 취약한 암호화 알고리즘을 사용할 수 없게 됩니다. 하지만, 오래된 사용자들은 SSL/TLS 의 상위 버전 자체를 사용할 수 없는 경우도 있기 때문에, 사용자 분포에 따라 취약한 알고리즘을 써야 하는 경우도 있을 겁니다.

가장 오른쪽에는 각 서버 어플리케이션의 버전에 따라 달라진 부분을 반영하기 위해 버전 정보를 기입할 수 있는 입력창이 위치해 있습니다. HSTS 와 OCSP Stapling 을 사용할 것인지에 따라 옵션을 선택할 수 있게 되어 있는 것도 눈에 띕니다. 

# generated 2021-02-08, Mozilla Guideline v5.6, nginx 1.17.7, OpenSSL 1.1.1d, modern configuration
# https://ssl-config.mozilla.org/#server=nginx&version=1.17.7&config=modern&openssl=1.1.1d&guideline=5.6
server {
    listen 80 default_server;
    listen [::]:80 default_server;

    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    ssl_certificate /path/to/signed_cert_plus_intermediates;
    ssl_certificate_key /path/to/private_key;
    ssl_session_timeout 1d;
    ssl_session_cache shared:MozSSL:10m;  # about 40000 sessions
    ssl_session_tickets off;

    # modern configuration
    ssl_protocols TLSv1.3;
    ssl_prefer_server_ciphers off;

    # HSTS (ngx_http_headers_module is required) (63072000 seconds)
    add_header Strict-Transport-Security "max-age=63072000" always;

    # OCSP stapling
    ssl_stapling on;
    ssl_stapling_verify on;

    # verify chain of trust of OCSP response using Root CA and Intermediate certs
    ssl_trusted_certificate /path/to/root_CA_cert_plus_intermediates;

    # replace with the IP address of your resolver
    resolver 127.0.0.1;
}

 

TLS 1.3 을 기준으로 NGINX 를 위한 설정을 생성해 보았습니다. HTTP 로 인입된 경우 HTTPS 로 스킴을 바꾸도록 301 리디렉션 하는 서버 컨텍스트가 앞에 위치해 있고, 443 포트로 HTTP/2.0 을 지원하는 엔드포인트를 열고 있습니다. 센스있게 IPv6 를 위한 포트 바인딩도 함께 추가되어 있네요!

그 외의 지시자들은 일름에서 유추할 수 있는 것처럼 프로토콜 버전의 지정, 인증서 파일의 위치, 세션 티켓 사용을 위한 옵션들이 지정되어 있습니다. 서버측의 prefer_server_ciphers 가 off 로 생성된 부분은 그 의도가 있을텐데 (아마도 NGINX 에서 TLS 1.3 사용시 기본 Cipher Suite 의 영향?) 자세한 건 문서를 좀 살펴봐야 할 것 같네요.


우리가 코드를 수정하거나 서버의 설정을 변경할 때 늘 하는 말이 있습니다. <지금 니가 무슨짓을 하는지 모른다면, 아무것도 하지 말아라> 라는 말이 바로 그것입니다. 각 설정이나 코드가 존재하는 것은 다 이유가 있어서 입니다. (예, 정말 가끔 이유 없이 존재하는게 없진 않습니다만...) 자동 생성된 설정을 참고하고 사용하는 것은 좋지만, 추가된 설정 항목들이 "왜" 들어갔는지에 대해서는 한번쯤 문서를 찾아보고 고개를 끄덕인 다음 Ctrl-C, V 하는 아름다운 자세를 견지하시기 바랍니다!

 

 

728x90
728x90

공식 문서의 taints 와 tolerations 에 대해서는 아래의 링크를 참고하면 된다. taints 는 노드에 적용되어 <수용 가능한 Pod 의 특성 정의>정도인 것 같고, tolerations 는 Pod 에 적용되어 <내가 동작할 수 있는 Node 를 선택하기 위한 특성 정의>라고 보면 될 것 같다.

 

테인트(Taints)와 톨러레이션(Tolerations)

노드 어피니티는 노드 셋을 (기본 설정 또는 어려운 요구 사항으로) 끌어들이는 파드의 속성이다. 테인트 는 그 반대로, 노드가 파드 셋을 제외할 수 있다. 톨러레이션 은 파드에 적용되며, 파드

kubernetes.io

 


노드에 설정된 taints 를 확인하기 위해서는 kubectl 에서 describe 옵션을 사용하면 된다.

$ kubectl describe node node01
Name:               node01
Roles:              <none>
Labels:             beta.kubernetes.io/arch=amd64
                    beta.kubernetes.io/os=linux
...
Annotations:        flannel.alpha.coreos.com/backend-data: null
                    flannel.alpha.coreos.com/backend-type: host-gw
...
                    volumes.kubernetes.io/controller-managed-attach-detach: true
CreationTimestamp:  Mon, 25 Jan 2021 07:43:27 +0000
Taints:             <none>
Unschedulable:      false
Lease:
  HolderIdentity:  node01
  AcquireTime:     <unset>

 

특정 노드에 특정 key-value 와 effect 값을 지정하여 taints 를 설정. 아래는 spray 를 key 로, mortein 을 value 로, effect 는 NoSchedule 로 지정한 예임

$ kubectl taint nodes node01 spray=mortein:NoSchedule
node/node01 tainted

 

Pod 을 생성할 때 배치된 Node 가 없다면 (이 경우에는 Taints 로 특정한 값이 부여된 Node 밖에 없다) Pod 이 생성되지 못하고 Pending 상태로 머무르며, 상세 에러 내용이 taints 와 tolerations 에 대한 내용이 노출된다.

$ kubectl get pods
NAME       READY   STATUS    RESTARTS   AGE
mosquito   0/1     Pending   0          88s

$ kubectl describe pod mosquito
Name:         mosquito
Namespace:    default
Priority:     0
Node:         <none>
...
...
Node-Selectors:  <none>
Tolerations:     node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                 node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type     Reason            Age               From               Message
  ----     ------            ----              ----               -------
  Warning  FailedScheduling  6s (x3 over 92s)  default-scheduler  0/2 nodes are available: 1 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate, 1 node(s) had taint {spray: mortein}, that the pod didn't tolerate.

 

Pod 을 Taints 가 지정된 Node 로 배포하기 위해서는 Pod 의 스펙에 관련된 값을 지정해 주어야 한다.

$ cat create_pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: bee
spec:
  containers:
  - image: nginx
    name: bee
  tolerations:
  - key: spray
    value: mortein
    effect: NoSchedule
    operator: Equal
    
$ kubectl create -f create_pod.yaml
pod/bee created

 

노드에 할당되어 있는 taints 값의 변경은 아래의 명령으로 수행할 수 있다. 예시는 controlplane 노드에 대하여 값을 부여하고 삭제하는 예시임.

$ kubectl taint nodes controlplane node-role.kubernetes.io/master:NoSchedule
node/controlplane tainted
$
$ kubectl taint nodes controlplane node-role.kubernetes.io/master:NoSchedule-
node/controlplane untainted
728x90
728x90

쿠버네티스 환경에서 자원들을 그룹 단위로 관리하기 위해서 사용하는 것이 labels 이다. labels 를 이용해 key-value 페어로 다양한 속성 값을 지정할 수 있으며, 이렇게 지정한 값들은 --selector 파라메터를 사용하여 아래와 같이 자원을 조회할 때 사용할 수 있다.

$ kubectl get pods --selector env=dev
NAME          READY   STATUS    RESTARTS   AGE
app-1-2k2pg   1/1     Running   0          71s
app-1-bbdsn   1/1     Running   0          71s
app-1-w7d94   1/1     Running   0          71s
db-1-dzcnf    1/1     Running   0          70s
db-1-f6f8m    1/1     Running   0          70s
db-1-lb72h    1/1     Running   0          70s
db-1-tlmjt    1/1     Running   0          70s

 

kubectl get 구문에서 자원의 형태에 관계 없이 목록을 가져오는 경우 all 을 쓰면 되는 것 같다. 

$ kubectl get all --selector env=prod
NAME              READY   STATUS    RESTARTS   AGE
pod/app-1-zzxdf   1/1     Running   0          6m43s
pod/app-2-hj6qs   1/1     Running   0          6m44s
pod/auth          1/1     Running   0          6m43s
pod/db-2-5nmrk    1/1     Running   0          6m43s

NAME            TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
service/app-1   ClusterIP   10.106.145.187   <none>        3306/TCP   6m43s

NAME                    DESIRED   CURRENT   READY   AGE
replicaset.apps/app-2   1         1         1       6m44s
replicaset.apps/db-2    1         1         1       6m43s

 

--selector 는 여러개의 key, value 를 값으로 받을 수 있는데, 아래와 같이 콤마(,)로 구분자를 넣어서 필요한 labels 를 나열하면 된다.

$ kubectl get pods --selector env=prod,bu=finance,tier=frontend
NAME          READY   STATUS    RESTARTS   AGE
app-1-zzxdf   1/1     Running   0          10m

 

labels 를 활용하여 ReplicaSet 을 만드는 yaml 설정은 아래와 같다.

// replicaset.yaml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: replicaset-1
spec:
  replicas: 2
  selector:
    matchLabels:
      tier: frontend
  template:
    metadata:
      labels:
        tier: frontend
    spec:
      containers:
      - name: nginx
        image: nginx
        
// Create replicaset
$ kubectl create -f replicaset-definition-1.yaml
replicaset.apps/replicaset-1 created

// Check
$ kubectl get pods --selector tier=frontend
NAME                 READY   STATUS    RESTARTS   AGE
app-1-2k2pg          1/1     Running   0          14m
app-1-bbdsn          1/1     Running   0          14m
app-1-w7d94          1/1     Running   0          14m
app-1-zzxdf          1/1     Running   0          14m
app-2-hj6qs          1/1     Running   0          14m
replicaset-1-9hcpb   1/1     Running   0          91s

 

 

 

CKA 자격 시험, namespace 를 활용한 몇 가지 커맨드 정리

Kubenetes 를 사용하면서 namespace 를 통해 큰 단위의 리소스 구분을 해줘야 하는 경우가 종종 생깁니다. namespace 를 감안하여 사용할 수 있는 몇 가지 명령어들 중 udemy 강의 실습에서 나온 커맨드를

ondemand.tistory.com


 

 

Certified Kubernetes Administrator (CKA) Practice Exam Tests

Prepare for the Certified Kubernetes Administrators Certification with live practice tests right in your browser - CKA

www.udemy.com

 

열심히 공부하고 있는 강의는 위의 링크를 참고하세요!
제휴마케팅을 통해 소정의 수수료를 지급 받을 수 있습니다

728x90
728x90

grafana 를 쓰면서 각 차트에 alert 를 걸어두는 경우가 종종있습니다. alert 는 기본적으로 텍스트 메세지 템플릿을 사용하기 때문에 이미지가 포함되어 있지 않습니다. 그런데 쓰다보면 항상 알람을 받고 grafana 에 접속해서 차트를 확인하는 일이 빈번하게 발생합니다. false alert 때문에 이런 동작을 반복하다보면 alert 를 무시하다 사고로 이어지는 경우도 생기죠.

문득 든 생각이 <alert 메세지가 차트가 보이면 좋겠구먼> 이었고 공식 가이드에 기술된 내용에 따라 설정을 해보기로 했습니다. 대략 방식을 보면 헤드리스 브라우저를 띄우고 해당 차트에 진입해서 png 파일을 생성해주고 고유 URL 을 만들어 주는 방식처럼 보입니다. 자세한 건 실제로 돌려봐야 알 수 있겠죠?

 

Grafana Image Renderer plugin for Grafana

Grafana Backend Image Renderer that uses headless chrome to capture images.

grafana.com


grafana 에 이미지 렌더링 플러그인 설치하기

이미지 렌더링을 위해서 먼저 grafana lab 에서 제작한 Grafana Image Renderer 플러그인을 설치해야 합니다. 플러그인을 설치하기 위해서는 먼저 Grafana 가 설치된 서버에서 grafana-cli 를 이용해 플러그인을 다운로드 받아야 합니다.

$ sudo /usr/sbin/grafana-cli plugins install grafana-image-renderer
installing grafana-image-renderer @ 2.0.0
from: https://grafana.com/api/plugins/grafana-image-renderer/versions/2.0.0/download
into: /var/lib/grafana/plugins

✔ Installed grafana-image-renderer successfully

Restart grafana after installing plugins . <service grafana-server restart>

 

rpm 으로 설치해서 그런것인지 OS 버전을 인식하지 않고 메세지를 내보내는 건지 모르겠습니다만, 플러그인 설치후 CentOS 6.x 기준으로 작성된 service 명령 안내가 나옵니다. 7.x 이후 버전을 쓰고 계시다면 아래와 같이 grafana 서버를 재기동 해주셔야 합니다. 

$ sudo systemctl restart grafana-server

 

차트를 이미지 파일로 렌더링하기

플러그인 설치후 grafana 재기동까지 끝났다면 이제 렌더링이 잘 되는지 시험해 볼 차례입니다. 화면을 그냥 캡쳐하는 것과 무엇이 다르냐 하겠습니다만, 우리의 목적은 단순 캡쳐가 아니라 alert 메세지에 이미지를 포함시키는 것이 목표임을 잊지 마시기 바랍니다. 

 

각 차트 상단의 toggle 버튼을 누르면 팝업 메뉴에서 `Share` 를 찾을 수 있습니다. 이 버튼을 누르면 나오는 `Share Panel` 에서 가장 아래에 있는 `Direct link rendered image` 버튼을 누르면 짠~ 하고 이미지가 나와야 합니다. 네, 꼭 나와야 합니다. ㅜㅜ

 

하지만 신은 저를 버리셨는지 차트가 나오지 않고 에러 페이지가 나왔습니다. 무엇이 문제인지 상세히 알려주지 않기 때문에 지금부터 grafana 와 렌더링 오류에 대한 원인을 찾기 위해 씨름을 좀 해야 합니다. 

 

grafana 렌더링 관련 디버그 로그 설정

공식 가이드에서는 디버깅을 위해 grafana.ini 의 [log] 항목에서 이미지 렌더러를 필터로 걸어서 쓸 것을 권고 하고 있습니다. 하지만 꼭 그렇게 설정하지 않아도 에러 로그를 확인하는데는 문제가 없습니다. 하지만 필요하다면 아래의 내용대로 grafana.ini 를 수정하고 재기동 하면 되겠습니다. 참고로 grafana.ini 의 경로는 패키지 설치 기준으로 `/etc/grafana/grafana.ini` 입니다. 

[log]
filters = rendering:debug

 

제 경우 굉장히 명확하게 에러가 나왔기 때문에 디버그로 설정할 필요는 없어 보였습니다. grafana 인스턴스와 이미지 렌더러 플러그인이 동일 머신에서 실행중이었기 때문에 localhost 콜을 했지만 https 로 호출된 것이 이유였습니다. 인증서 정보가 맞지 않아 ERR_CERT_COMMON_NAME_INVALID 에러가 발생한 것을 확인할 수 있습니다. 참고로 에러 로그의 경로는 `/var/log/grafana/grafana.log` 입니다. 

t=2021-01-20T17:51:05+0900 lvl=eror msg="Browser request failed" logger=plugins.backend pluginId=grafana-image-renderer failure=net::ERR_CERT_COMMON_NAME_INVALID url="https://localhost:3000/d-solo/Sr8H67hWz/system-metrics?orgId=1&from=1611129063221&to=1611132663221&var-Cluster=All&var-Instance=All&panelId=7&width=1000&height=500&tz=Asia%2FTokyo&render=1" method=GET

 

 

grafana.ini 의 [rendering] 설정 수정

SSL 인증서의 문제는 해결이 어렵지 않습니다. grafana-image-renderer 가 사용할 프로토콜은 grafana.ini 에서 지정할 수 있습니다. grafana.ini 파일을 열고 아래와 같이 설정하시기 바랍니다. 로컬 환경에 대한 설정이기 때문에 https 연결 과정의 에러를 무시해도 괜찮습니다.  

[plugin.grafana-image-renderer]
rendering_ignore_https_errors = true

 

설정을 변경했으면 grafana-server 를 재기동 해줍니다. 

$ sudo systemctl restart grafana-server

 

재기동이 완료되면 앞서 진행했던 것처럼 차트에서 Share 메뉴를 선택하고 `Direct link rendered image` 를 선택합니다. 이번에는 문제 없이 이미지가 화면에 출력되었습니다. 이렇게 생성된 PNG 파일은 아래의 경로에서 확인할 수 있습니다. 

$ sudo ls -al /var/lib/grafana/png
total 444
drwx------ 2 grafana grafana    134 Jan 21 10:53 .
drwxr-xr-x 5 grafana grafana     66 Jan 21 12:04 ..
-rw-r--r-- 1 grafana grafana 111403 Jan 21 10:46 96YKmq7MmGRTm8rArAMY.png
-rw-r--r-- 1 grafana grafana 109553 Jan 21 10:14 IQkelP0MQ70KVvFyEdeO.png
-rw-r--r-- 1 grafana grafana 111403 Jan 21 10:48 npMRvBsSuPxwDhRESL2n.png
-rw-r--r-- 1 grafana grafana 112893 Jan 21 10:53 xsCpyt0Idc39bKVjf9FL.png

 

이미지 렌더링에 대한 요청이 많은 것이 아니라 그랬을까요? 공식 문서에서 권장하는 16GB 의 메모리 공간에 택도 없이 부족한 경량 가상머신 장비였지만 렌더링 속도도 느리지 않았고 생성 과정에서 문제도 발생하지 안았습니다. 

 

Slack Notification 의 설정? 결국 실패

결국 중요한 것은 이렇게 만든 이미지가 슬랙과 같은 서비스로 잘 연동되는지가 중요합니다. 문제는 이렇게 생성된 이미지들이 슬랙에서 보이려면 퍼블릭 인터넷 환경에서 접근이 가능해야 한다는 점이었습니다. 특히 회사 내부에서 사용하는 시스템의 중요한 지표라면 외부에서 아무나 액세스 할 수 있는 공간에 차트를 저장하는 것은 보안상 문제가 될 수 밖에 없습니다. 

결론을 먼저 말씀드리긴 했습니다만, 이렇게 생성한 슬랙으로 전달되지 않았습니다. grafana notification 은 발송이 되지만 이미지 주소가 내부 도메인, 내부 주소를 사용하고 있기 때문에 슬랙이 읽어들여 채널에 렌더링 할 수가 없었습니다. 

 

grafana 는 위와 같이 Notification 의 대상이 될 수 있는 type 을 제공하고 있습니다. 세번째 컬럼에서 <external only> 로 표기된 서비스들은 설명드린 것처럼 퍼블릭 인터넷 구간에서 접근이 가능한 스토리지를 사용해야만 합니다. S3 와 같은 것이 대표적이겠죠. 이 표기가 없는 Type 의 경우 local 스토리지를 사용할 수 있다고 이해하고 슬랙을 사용해보고자 했지만, 결과적으로는 잘 동작하지 않았습니다. 

 

에필로그

grafana 에서 슬랙을 액세스 하는 방식은 Incoming Webhook 입니다. 이 방식에서는 파일의 업로드가 제공되지 않습니다. 메세지만 전송할 수 있는 것이지요. 내부 주소를 쓰게 되면 외부 환경에서 VPN 등의 연결이 필요한 상황이 또 발생하니 좋은 접근이 되기는 어렵습니다. 애초부터 문제 상황을 한번에 알기 위해서 이미지를 메세지에서 보여주려는 것이었는데 보안상 불가하다가 된 상황입니다. 

외부 액세스가 문제 없는 이미지들이라면 (과연 존재할까요?) 공식 문서의 external_image_storage 섹션을 확인하여 S3 등을 타겟으로 설정해서 쓰시면 되겠습니다. 결과적으로 도움이 안되는 포스팅이 되버린 것 같아 속상합니다만...ㅎㅎ 사내용 이미지를 grafana notification + slack 으로 해결해 보신 경우가 있다면 댓글로 도움 말씀 부탁드리겠습니다!


 

Grafana 를 새로운 버전으로 업그레이드 해보자

한 번 설치해서 잘 돌아가는 시스템을 업그레이드 하는 것은 참 귀찮은 일입니다. 하지만 새로운 버전에서만 쓸 수 있는 기능이 생겼고 이를 사용해야 하는 경우엔 어쩔수 없이 업그레이드를 선

ondemand.tistory.com

 

728x90

+ Recent posts