Software Workshop

elasticsearch를 설치하고 클러스터에 접근할 때 credential 관련 에러가 발생할 수 있다.

curl http://localhost:9200/_cluster/health

curl: (52) Empty reply from server

ES에서 empty reply 증상이 발생하면 ES 클러스터가 아예 시작하지 않았거나 https로 접근하지 않아서 요청을 무시하는 것이 원인이다.

https로 변경하고 -k 옵션을 추가해서 자세한 원인을 살펴보면 다음과 같다.

curl -k https://10.152.183.118:9200/_cluster/health
{
  "error": {
    "root_cause": [
      {
        "type": "security_exception",
        "reason": "missing authentication credentials for REST request [/_cluster]",
        "header": {
          "WWW-Authenticate": [
            "Basic realm=\"security\" charset=\"UTF-8\"",
            "Bearer realm=\"security\"",
            "ApiKey"
          ]
        }
      }
    ],
    "type": "security_exception",
    "reason": "missing authentication credentials for REST request [/_cluster]",
    "header": {
      "WWW-Authenticate": [
        "Basic realm=\"security\" charset=\"UTF-8\"",
        "Bearer realm=\"security\"",
        "ApiKey"
      ]
    }
  },
  "status": 401
}

직접적인 원인은 credential(id/password)를 전달하지 않아서 발생한 것이다.

간단한 해결방법으로는 ElasticSearch를 7.x로 낮춰서 설치하고 사용하는 방법이 있다. ElasticSearch 7에서는 credential을 필수로 요구하지 않는다.

다소 복잡한 해결방법으로는 https로 접근하고 인증서 파일들을 찾아서 클라이언트가 사용할 수 있도록 설정하는 것이다.

https로 접근하려면 도메인을 사용하고 해당 도메인으로 발급된 TLS 인증서를 사용해야 한다. curl과 같은 클라이언트에서 -k 옵션을 이용해서 유효하지 않은 인증서를 무시하는 방법도 있지만, 애플리케이션에서는 이런 옵션을 사용할 수 없기 때문에 인증서를 파일로 저장해두고 사용해야 한다.

일반적으로 인증서는 1) 자체 서명을 해서 직접 만드는 방법이 있는가 하면 2) 공인 인증 기관에서 발급받은 인증서를 사용하는 방법도 있고 3) K8s 환경에서 cert-manager를 이용해서 자가 발급받는 방법도 있다.

자체 서명으로 자가 발급하는 방법

openssl genrsa -out ca.key 2048
openssl req -new -x509 -key ca.key -sha256 -subj "/CN=내인증기관이름" -days 3650 -out ca.crt
openssl genrsa -out tls.key 2048
openssl req -new -key tls.key -out tls.csr -subj "/CN=내ES서버이름"
openssl x509 -req -in tls.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out tls.crt -days 3650 -sha256

이제 ca.crt, ca.key, tls.crt, tls.csr, tls.key 인증서 파일이 생겼다. 인증서 파일을 ElasticSearch 서비스가 접근할 수 있는 위치로 잘 옮겨두고, elasticsearch.yml 파일에 해당 위치를 지정하면 된다.

xpack.security.enabled: true
xpack.security.transport.ssl.enabled: true
xpack.security.transport.ssl.verification_mode: certificate
xpack.security.transport.ssl.key: /path/to/tls.key
xpack.security.transport.ssl.certificate: /path/to/tls.crt
xpack.security.transport.ssl.certificate_authorities: ["/path/to/ca.crt"]
xpack.security.http.ssl.enabled: true
xpack.security.http.ssl.key: /path/to/tls.key
xpack.security.http.ssl.certificate: /path/to/tls.crt
xpack.security.http.ssl.certificate_authorities: ["/path/to/ca.crt"]

ElasticSearch 서비스를 재시작한 다음에, curl로 인증서 파일을 이용해서 접근할 수 있다. curl과 같은 클라이언트가 접근할 수 있는 위치에 ca.crt 파일을 복사해둘 필요가 있다.

curl --cacert /path/to/ca.crt -u "id:password" https://localhost:9200/_cluster/health

공인 인증 기관에서 발급받은 인증서를 사용하는 방법

공인 인증서 파일을 ElasticSearch 서비스가 접근할 수 있는 위치에 놓고 위의 방법과 동일하게 elasticsearch.yml을 설정하고 재시작하면 된다.

K8s의 cert-manager를 이용하는 방법

cert-manager가 잘 설치되어 있고, issuer나 clusterissuer도 잘 설정되어 있다고 가정하겠다.

다음과 같은 manifest를 이용하여 clusterissuer에게 인증서 발급 요청을 한다. 그러면 certificate 타입의 secret이 생성된다. elasticsearch-tls라는 이름의 secret으로 이용할 수 있다.

es-certificate.yml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: elasticsearch-certificate
  namespace: default
spec:
  secretName: elasticsearch-tls
  duration: 2160h # 90d
  renewBefore: 360h # 15d
  commonName: "elasticsearch.yourdomain.com"
  dnsNames:
  - "elasticsearch.yourdomain.com"
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
kubectl apply -f es-certificate.yaml
elasticsearch.yml
xpack.security.enabled: true
xpack.security.http.ssl.enabled: true
xpack.security.http.ssl.key: /usr/share/elasticsearch/config/certificates/tls.key
xpack.security.http.ssl.certificate: /usr/share/elasticsearch/config/certificates/tls.crt
xpack.security.http.ssl.certificate_authorities: ["/usr/share/elasticsearch/config/certificates/ca.crt"]

elasticsearch.yml을 이렇게 마련해두고, 해당 경로에 certificate이 파일 형태로 마운트되도록 manifest를 작성한다. (K8s에서 certificate 타입의 secret은 보통 파일로 마운트해서 사용한다.)

es-pod.yml
spec:
  containers:
  - name: elasticsearch
    image: elasticsearch:7.10.0
    volumeMounts:
    - name: cert
      mountPath: "/usr/share/elasticsearch/config/certificates"
      readOnly: true
  volumes:
  - name: cert
    secret:
      secretName: elasticsearch-tls