k8s의 데이터 전송 기반에 스크래핑 기능을 추가한 이야기

Selenium/Appium Advent Calendar 2019 16일째(?)가 됩니다! (늦어서 미안해 미안해)

배경



당사의 제품에서는 데이터 전송의 기반을 k8s(EKS)로 구축하고 있습니다.
데이터 전송은 k8s의 job 로 실시하고 있어 데이터 전송의 처리는 Embulk 를 사용하고 있습니다.

이번에는 데이터 전송에 스크래핑 기능을 추가했을 때의 이야기를 드리겠습니다.

사이드카 패턴



기분으로서, 데이터 전송의 메인 컨테이너를 Selenium의 Driver를 실는 것으로 크게 하고 싶지 않다고 생각하고 있었습니다. 그 때문에 Selenimu에 의한 스크래핑 기반을 사이드카 패턴으로 구성하려고 생각했습니다.

사이드카에 대한 자세한 내용은 Brendan Burns, David Oppenheimer 등의 논문( Design patterns for container-based distributed systems )을 읽고 서적을 읽어 주시는 것으로, 개요를 설명하면 사이드카와는 포드 안에서 메인 컨테이너를 보조하는 것 같은 컨테이너 의 구성을 말합니다.

아래의 예에서 Web Server는 메인 컨테이너에 올려 로그의 저장을 사이드카로 하도록 구성하고 있습니다.



이번에 말하면 스크래핑과 데이터 전송 처리는 메인 컨테이너에 갖게하고, Selenium의 실행 환경은 다른 컨테이너로 기동하는 사이드카의 구조를 작성했습니다.

스크래핑에서 데이터 전송 정책



우리는 데이터 전송에 Embulk를 사용합니다. Embulk에는 로컬 파일을 전송하는 플러그인이 있으므로 다음 정책으로 스크래핑한 데이터를 전송하도록 했습니다.
  • 스크래핑을 수행하고 얻은 데이터를 JSON 데이터로 만듭니다
  • 생성 된 JSON 데이터를 Embulk로 전송

  • Selenium의 실행 환경으로서는 Pod상에 Selenium Grid를 구성하도록 하고 있습니다. 구체적으로는 하나의 포드에 Selenium Hub 컨테이너와 Node 컨테이너를 시작하고 있습니다.



    실제로 부팅



    다음은 실제로 시작하는 Pod의 describe한 것입니다.
    한 포드에서 Selenium Hub와 Node가 사이드카로 공존하고 있음을 알 수 있습니다.
    Name:               sample-job-215127-m26g7
    Namespace:          default
    Priority:           0
    PriorityClassName:  <none>
    Node:               ip-10-80-88-198.***/10.80.88.198
    Start Time:         Wed, 18 Dec 2019 01:49:03 +0000
    Labels:             controller-uid=9187b356-2138-11ea-a061-0e36cb33bc04
                        job-name=sample-job-215127
                        prometheus.io/path=metrics
                        prometheus.io/port=9010
                        prometheus.io/scrape=true
    Annotations:        cluster-autoscaler.kubernetes.io/safe-to-evict: false
    Status:             Running
    IP:                 10.80.92.39
    Controlled By:      Job/sample-job-215127
    Containers:
      worker:
        Container ID:  docker://4547f0a3b43fbf14c4850e26d71e9db89799f263125d679724e5c1a09289e4d6
        Image:         ***/worker.beta.sample.io:5c0f855874849b1d29ee5f1fe7c720ca835d60c3
        Image ID:      docker-pullable://***/worker.beta.sample.io@sha256:9a1771dff18778d1995359dd1eb056d13a2c0f1b7b23a3c03198771ee978024c
        Port:          9010/TCP
        Host Port:     0/TCP
        Args:
          sample:run[215127,false]
        State:          Running
          Started:      Wed, 18 Dec 2019 01:49:04 +0000
        Ready:          True
        Restart Count:  0
        Limits:
          cpu:     2
          memory:  2Gi
        Requests:
          cpu:     2
          memory:  2Gi
        Environment Variables from:
          sample-config  ConfigMap  Optional: false
          sample-secret  Secret     Optional: false
        Environment:     <none>
        Mounts:
          /tmp from tmp-volume (rw)
          /var/run/secrets/kubernetes.io/serviceaccount from default-token-4nw9k (ro)
      selenium:
        Container ID:   docker://44c1d93596ff64ba63d783801ec4200d6f6fc6445305473904f5ce1e96fd686e
        Image:          selenium/hub:3.141.59
        Image ID:       docker-pullable://selenium/hub@sha256:6f6bdd8d5ce5cd8d7be42ded88bc3dbbdf7a60ec32c908eb26dd31b37f69589a
        Port:           <none>
        Host Port:      <none>
        State:          Running
          Started:      Wed, 18 Dec 2019 01:49:04 +0000
        Ready:          True
        Restart Count:  0
        Limits:
          cpu:     500m
          memory:  2000Mi
        Requests:
          cpu:     500m
          memory:  2000Mi
        Environment Variables from:
          sample-config  ConfigMap  Optional: false
          sample-secret  Secret     Optional: false
        Environment:     <none>
        Mounts:
          /tmp from tmp-volume (rw)
          /var/run/secrets/kubernetes.io/serviceaccount from default-token-4nw9k (ro)
      chrome:
        Container ID:   docker://cd880ec332d2af37efd91535ebdbad0e128295aa58f6255d6c093668306438cf
        Image:          selenium/node-chrome:3.141.59
        Image ID:       docker-pullable://selenium/node-chrome@sha256:5e37ffdeae0864dd7f0df63adafcc9428766d79976a645853bc1e20d7487ff4f
        Port:           <none>
        Host Port:      <none>
        State:          Running
          Started:      Wed, 18 Dec 2019 01:49:04 +0000
        Ready:          True
        Restart Count:  0
        Limits:
          cpu:     500m
          memory:  2000Mi
        Requests:
          cpu:     500m
          memory:  2000Mi
        Environment Variables from:
          sample-config  ConfigMap  Optional: false
          sample-secret  Secret     Optional: false
        Environment:     <none>
        Mounts:
          /tmp from tmp-volume (rw)
          /var/run/secrets/kubernetes.io/serviceaccount from default-token-4nw9k (ro)
    Conditions:
      Type              Status
      Initialized       True
      Ready             True
      ContainersReady   True
      PodScheduled      True
    Volumes:
      tmp-volume:
        Type:    EmptyDir (a temporary directory that shares a pod's lifetime)
        Medium:
      default-token-4nw9k:
        Type:        Secret (a volume populated by a Secret)
        SecretName:  default-token-4nw9k
        Optional:    false
    QoS Class:       Guaranteed
    Node-Selectors:  <none>
    Tolerations:     node.kubernetes.io/not-ready:NoExecute for 300s
                     node.kubernetes.io/unreachable:NoExecute for 300s
    Events:
      Type    Reason     Age   From                                                      Message
      ----    ------     ----  ----                                                      -------
      Normal  Scheduled  18s   default-scheduler                                         Successfully assigned default/sample-job-215127-m26g7 to ip-10-80-88-198.***
      Normal  Pulled     17s   kubelet, ip-10-80-88-198.a***  Container image "***/worker.beta.sample.io:5c0f855874849b1d29ee5f1fe7c720ca835d60c3" already present on machine
      Normal  Created    17s   kubelet, ip-10-80-88-198.a***  Created container
      Normal  Started    17s   kubelet, ip-10-80-88-198.a***  Started container
      Normal  Pulled     17s   kubelet, ip-10-80-88-198.a***  Container image "selenium/hub:3.141.59" already present on machine
      Normal  Created    17s   kubelet, ip-10-80-88-198.a***  Created container
      Normal  Started    17s   kubelet, ip-10-80-88-198.a***  Started container
      Normal  Pulled     17s   kubelet, ip-10-80-88-198.a***  Container image "selenium/node-chrome:3.141.59" already present on machine
      Normal  Created    17s   kubelet, ip-10-80-88-198.a***  Created container
      Normal  Started    17s   kubelet, ip-10-80-88-198.a***  Started container
    

    이와 같이 구성함으로써 메인 컨테이너를 비대화시키지 않고 Selenimu의 실행 환경을 구축할 수 있었습니다. 전송 처리가 완료되면 Pod 마다 삭제해 버리면 Selenium의 환경이 불필요하게 남는 일도 없기 때문에 관리도 매우 편합니다.

    다만, 사이드카 구성의 Pod에서 메인 컨테이너가 종료했을 때의 Pod의 정지 처리에 대해서, 현재는 메인 컨테이너의 정지해도 사이드카는 정지되지 않습니다.

    여기 에서 논의된 사이드카 기능이 릴리스될 때까지 중지 처리를 구현해야 합니다.

    요약



    이번은 k8s상의 데이터 전송 기반에 Selenium에 의한 스크래핑 기능을 포함한 이야기를 했습니다. 비교적 간단하고 관리도 편하게 스크래핑 기반을 구축할 수 있으므로 앞으로 여러가지 형태로 활용해 나가고 싶습니다.

    좋은 웹페이지 즐겨찾기