9
5

More than 5 years have passed since last update.

Selenium Grid from Concourse on Kubernetes

Last updated at Posted at 2017-06-08
1 / 27

Concourse CI/CD Meetup Tokyo #6の登壇資料です。


アジェンダ

  • Selenium Grid
  • Selenium Grid from Concourse
  • Selenium Grid from Concourse on Kubernetes
  • やってみよう
  • Future Work: Selenium Grid from Concourse on Kubernetes on AWS

Selenium Grid

複数のマシンやコンテナにSeleniumのテストを分散並列実行させるためのプロキシ

用語

WebDriver プログラムしたとおりにブラウザを動かすドライバ。プログラムから見れば、ドライバ=ブラウザ。同じマシン・コンテナ内で実際のブラウザの起動やコントロールを行う
RemoteWebDriver 別プロセスで動いているWebDriverを動かすWebDriver
Selenium Node RemoteWebDriver Serverが実行されているマシン、コンテナ


Selenium Grid from Concourse

ConcourseタスクからSeleniumテストの分散・並列実行

メリット

テストの所要時間短縮
複数ブラウザを対象にテスト
BrowserStack、SauceLabsなどの有償サービスを使わなくてもできる)

デメリット

ConcourseとSelenium Gridのクラスタをそれぞれセットアップする手間
Selenium GridのURLがローカルと本番で異なる


Selenium Grid from Concourse on Kubernetes

Kubernetes上でConcourseとSelenium Gridをホストする

メリット

ConcourseとSelenium Gridのクラスタをそれぞれセットアップ
Selenium GridのURLがローカルと本番で一緒 - ローカル環境でも本番環境でも全く同じパイプライン・タスクでSeleniumテストの分散・並列実行ができる


やってみよう

  • ローカルKubernetes(K8S)クラスタ作成
  • K8SにSelenium Gridをインストール
  • ChromeノードにVNCで接続
  • Pythonのreplでテストコードを書く
  • pytestでテスト実行
  • K8SにConcourseをインストール
  • Concourse Job/Taskからテスト実行
  • テスト実行の様子を観察
  • プロセス並列で実行
  • 上級編: いちいちgit commit&pushせずにテストしたい

やってみよう

  • ローカルKubernetes(K8S)クラスタ作成
  • K8SにSelenium Gridをインストール
  • ChromeノードにVNCで接続
  • Pythonのreplでテストコードを書く
  • pytestでテスト実行
  • K8SにConcourseをインストール
  • Concourse Job/Taskからテスト実行 ←Concourseの話
  • テスト実行の様子を観察
  • プロセス並列で実行
  • 上級編: いちいちgit commit&pushせずにテストしたい ←Concourseの話

ローカルK8Sクラスタ作成

minikube start --cpus 4 --memory 6144

  • CPUは4コア(Concourse Web/Workerでそれぞれ1コア+その他で2コア)
  • メモリは多めに6GB確保
    •  minikubeVMのデフォルトメモリサイズは1024M
    •  MySQL, Redis, Selenium Grid Hub, Selenium Node Firefox/Chromeを起動するとだいたい4GBくらい
    • Concourse Web/Workerでだいたい1GBくらい

Selenium Gridインストール

helm install stable/selenium \
  --set chromeDebug.enabled=true \
  --set firefoxDebug.enabled=true
  --name selenium-grid
  • ただし、2017/06/05現在下記PRのマージが必要
    • https://github.com/kubernetes/charts/pull/1239
    • それまでは自分でHelm Chartをビルド&インストール
    • helm package . && helm install selenium-0.1.1.tgz --set chromeDebug.enabled=true --set firefoxDebug.enabled=true --name selenium-grid

ChromeノードにVNCで接続

kubectl port-forward --namespace default \
  $(kubectl get pods --namespace default \
    -l app=selenium-grid-selenium-chrome-debug \
    -o jsonpath='{ .items[0].metadata.name }') 5900

macOS標準装備のVNCクライアントで接続

open vnc://127.0.0.1:5900


SeleniumのRemoteWebDriver実行用コンテナ作成

kubectl run selenium-python --image=google/python-hello
export PODNAME=`kubectl get pods --selector="run=selenium-python" --output=template --template="{{with index .items 0}}{{.metadata.name}}{{end}}"`

# 初回はdocker pullに時間がかかるので数分待ってから・・・
kubectl exec --stdin=true --tty=true $PODNAME bash
pip install selenium
python

Pythonのreplでテストコードを書く

from selenium import webdriver
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities

driver = webdriver.Remote(
  command_executor='http://selenium-grid-selenium-hub:4444/wd/hub',
  desired_capabilities=getattr(DesiredCapabilities, "CHROME")
)
driver.get("http://google.com")

assert "google" in driver.page_source
driver.close()

pytestでテスト実行

pip install -U pytest
import time

from selenium import webdriver
from selenium.webdriver.common.desired_capabilities im
port DesiredCapabilities

def test_chrome():
        driver = webdriver.Remote(
          command_executor='http://selenium-grid-selen
ium-hub:4444/wd/hub',
          desired_capabilities=getattr(DesiredCapabili
ties, "CHROME")
        )
        driver.get("http://google.com")
        time.sleep(5)
        assert "google" in driver.page_source
        driver.close()
pytest

K8SにConcourseをインストール

helm install stable/concourse --name concourse
export POD_NAME=$(kubectl get pods --namespace default -l "app=concourse-web" -o jsonpath="{.items[0].metadata.name}")
    echo "Visit http://127.0.0.1:8080 to use Concourse"
    kubectl port-forward --namespace default $POD_NAME 8080:8080
open http://127.0.0.1:8080/

image.png


Concourseパイプラインをつくる

jobs:
- name: e2e
  plan:
  - task: pytest-selenium
    config:
      platform: linux
      image_resource:
        type: docker-image
        source: {repository: google/python-hello}
      run:
        path: bash
        args:
        - -exc
        - |
          apt-get update -y
          apt-get install curl -y
          pip install selenium
          pip install -U pytest
          cat << EOS > e2e_test.py
          import time

          from selenium import webdriver
          from selenium.webdriver.common.desired_capabilities import DesiredCapabilities

          def test_chrome():
            driver = webdriver.Remote(
              command_executor='http://selenium-grid-selenium-hub:4444/wd/hub',
              desired_capabilities=getattr(DesiredCapabilities, "CHROME")
            )
            driver.get("http://google.com")
            time.sleep(5)
            assert "google" in driver.page_source
            driver.close()

          EOS
          pytest
$ fly login -t k8s -c http://127.0.0.1:8080 -u concourse -p concourse
$ fly -t k8s set-pipeline -p e2e -c e2e.yaml
$ fly -t k8s unpause-pipeline -p e2e

パイプライン感なし

image.png


Concourse Job/Taskからテスト実行

[+]ボタンをクリックするか、fly -t k8s trigger-job -j e2e/e2eを実行


Concourse Job/Taskからテスト実行

pytest実行中。RemoteWebDriver経由でSelenium NodeのChromeが動いているはず。


テスト実行の様子を観察

実際、VNCクライアントで見てみるとChromeが動いていることがわかる。

しばらく待つとテストが通る。


プロセス並列で実行

pytest-xdistを使う。

pip install --upgrade setuptools
pip install pytest-xdist

...

pytest -n <プロセス数>

setuptoolsをアップグレードしないと-nオプションが認識されない。pythonあるある?


上級編: いちいちgit commit&pushせずにテストしたい

resource_types:
- name: kube-broker
  type: docker-image
  source:
    # Replace the url
    repository: mumoshu/concourse-kube-broker-resource
resources:
- name: test-sources
  type: kube-broker
  source:
    configmap: foobar
    path: /app
    k8s_ca: {{k8s_ca_base64}}
    k8s_service_account_token: {{k8s_service_account_token_base64}}
jobs:
- name: e2e
  plan:
  - get: test-sources
  - task: pytest-selenium
    config:
      inputs:
      - name: test-sources
      platform: linux
      image_resource:
        type: docker-image
        source: {repository: google/python-hello}
      run:
        path: bash
        args:
        - -exc
        - |
          apt-get update -y
          apt-get install curl -y
          pip install selenium
          pip install -U pytest
          cd test-sources
          pytest

以下のようなconfigmapで、resourceのinputをとってくる元podを指定

$ kubectl get configmap foobar -o yaml
apiVersion: v1
data:
  kubernetes.pod.name: selenium-python-2632905994-7ck0z
kind: ConfigMap
metadata:
  creationTimestamp: 2017-06-06T04:31:48Z
  name: foobar
  namespace: default
  resourceVersion: "75860"
  selfLink: /api/v1/namespaces/default/configmaps/foobar
  uid: 0e436817-4a71-11e7-affc-080027d5f559

Concourse ResourceからK8S APIにアクセスするために利用するトークンを取得

token_name=$(kubectl get secret | grep default| awk '{ print $1 }')
SERVICE_ACCOUNT_TOKEN=$(kubectl get secret $token_name -o jsonpath={.data.token})

トークンとクラスタのCAをパラメータにパイプラインを作成

fly -t k8s set-pipeline -p e2e-2 -c e2e-2.yml -v k8s_ca_base64=$(cat $HOME/.minikube/ca.crt | base64) -v k8s_service_account_token_base64=$SERVICE_ACCOUNT_TOKEN
fly -t k8s unpause-pipeline -p e2e-2

kube-broker-resource

concourse-smuggler-resourceというadhocなconcourseリソースつくるメタなConcourseリソースをベースに作成

smuggler.yml

#filter_raw_request: true
commands:
  check: |
    KUBE_URL=${SMUGGLER_k8s_api_endpoint_url:-https://kubernetes}
    NAMESPACE=${SMUGGLER_k8s_namespace:-default}
    KUBECTL="/usr/local/bin/kubectl --server=$KUBE_URL --namespace=$NAMESPACE"

    # configure SSL Certs if available
    if [[ "$KUBE_URL" =~ https.* ]]; then
      KUBE_CA_BASE64="${SMUGGLER_k8s_ca}"
      KUBE_SERVICE_ACCOUNT_TOKEN_BASE64=${SMUGGLER_k8s_service_account_token}
      CA_PATH="/root/.kube/ca.pem"

      mkdir -p /root/.kube
      echo "$KUBE_CA_BASE64" | base64 -d > $CA_PATH
      KUBE_SERVICE_ACCOUNT_TOKEN=$(echo "$KUBE_SERVICE_ACCOUNT_TOKEN_BASE64" | base64 -d)

      KUBECTL="$KUBECTL --certificate-authority=$CA_PATH --token=$KUBE_SERVICE_ACCOUNT_TOKEN"
    fi

    $KUBECTL get configmap ${SMUGGLER_configmap} >/dev/null

    $KUBECTL get configmaps ${SMUGGLER_configmap} -o json \
      | jq -r '.data["kubernetes.pod.name"]' > kubernetes-pod-name

    kube_pod_name=$(cat kubernetes-pod-name)

    if [ "$kube_pod_name" == "" ]; then
      echo No kubernetes pod named $kube_pod_name exists 1>&2
      exit 1
    fi

    mkdir -p /copied
    $KUBECTL cp $kube_pod_name:${SMUGGLER_path} /copied${SMUGGLER_path} 1>&2

    find /copied${SMUGGLER_path} -type f -print0 | sort -z | xargs -0 sha1sum | sha1sum | cut -d' ' -f 1 > current-version

    current_version=$(cat current-version)

    if [ "$current_version" != "${SMUGGLER_VERSION_ID:-}" ]; then
      echo "$current_version" >> ${SMUGGLER_OUTPUT_DIR}/versions
    fi

  in: |
    KUBE_URL=${SMUGGLER_k8s_api_endpoint_url:-https://kubernetes}
    NAMESPACE=${SMUGGLER_k8s_namespace:-default}
    KUBECTL="/usr/local/bin/kubectl --server=$KUBE_URL --namespace=$NAMESPACE"

    # configure SSL Certs if available
    if [[ "$KUBE_URL" =~ https.* ]]; then
      KUBE_CA_BASE64="${SMUGGLER_k8s_ca}"
      KUBE_SERVICE_ACCOUNT_TOKEN_BASE64=${SMUGGLER_k8s_service_account_token}
      CA_PATH="/root/.kube/ca.pem"

      mkdir -p /root/.kube
      echo "$KUBE_CA_BASE64" | base64 -d > $CA_PATH
      KUBE_SERVICE_ACCOUNT_TOKEN=$(echo "$KUBE_SERVICE_ACCOUNT_TOKEN_BASE64" | base64 -d)

      KUBECTL="$KUBECTL --certificate-authority=$CA_PATH --token=$KUBE_SERVICE_ACCOUNT_TOKEN"
    fi

    $KUBECTL get configmap ${SMUGGLER_configmap} >/dev/null

    $KUBECTL get configmaps ${SMUGGLER_configmap} -o json \
      | jq -r '.data["kubernetes.pod.name"]' > kubernetes-pod-name

    kube_pod_name=$(cat kubernetes-pod-name)

    if [ "$kube_pod_name" == "" ]; then
      echo No kubernetes pod named $kube_pod_name exists 1>&2
      exit 1
    fi

    $KUBECTL cp $kube_pod_name:${SMUGGLER_path} ${SMUGGLER_DESTINATION_DIR} 1>&2

Future Work: Selenium from Concourse on Kubernetes on AWS

  • AWS Device Farm --> Browser --> Temporary Endpoint --> (VPN -->) Target Webapp
  • AWS Device Farm iOS、Androidデバイスをリモートで時間貸ししてくれるサービス
    • ただし、VPCにはつなげられない、VPNも使えないという制限がある。リモートで借りたデバイスはインターネットにしかアクセスできないということ。
    • 課題 「まだインターネットに公開したくない、開発中Webアプリ」をDevice Farmでどうやって安全にテストする?
  • 案: テスト対象のWebアプリに、インターネットからアクセス可能な一時URLを発行する
    • テスト対象のWebアプリがローカルマシンにある場合は、VPNを使ってインターネットからのアクセスをローカルK8S内で動いているWebアプリにフォワードする
    • 構成 Device --> 一時URL --> テスト環境K8S --> kube-openvpn --> ローカル環境K8S --> テスト対象Webアプリ
  • メリット スマフォからのE2Eテスト用パイプラインさえもローカルで動かせる! DevProd Parity!

参考リンク


まとめ

  • ConcourseでSeleniumの分散・並列テスト実行
  • ローカルマシンでも本番サーバでも全く同じパイプラインでテストできる
  • Concourse on K8Sならコードをいちいちpushしなくてもパイプラインをテストできる

自己紹介 & おわり

twitter/github/slack.k8s.io: @mumoshu (むもしゅ)
Primary maintainer of kubernetes-incubator/kube-aws

Fin :bow: :clap:

9
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
5