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を構成するようにしています。具体的には1つのPodにSelenium HubコンテナとNodeコンテナを立ち上げています。
実際に起動させる
以下は実際に起動しているPodのdescribeしたものです。
1つのPodで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によるスクレイピング機能を組み込んだ話をしました。比較的簡単かつ管理も楽にスクレイピング基盤を構築できるので、今後いろんな形で活用していきたいと思います。