Cloudera Hue をCDHから切り離してDockerで運用出来ないか検討してみる

qiita.com

1日遅れになってしまいましたが、、、
この記事は MicroAd (マイクロアド) Advent Calendar 2020 - Qiita20日目の記事です。

昨日は dai08srhg - Qiita のEmbulkの話でした。
(´-`).。oO(EmbulkはHiveやHDFS系のプラグインがアップデートあると嬉しいなぁと思う今日この頃) qiita.com

さて、本題ですが、今回はHueとDockerを使った話です。 Cloudera Hueは、 CDHに含まれているコンポーネントの一つ ですが、Hueは他のコンポーネントに比べて他のコンポーネントと協調して動作するものではなく基本的にクラスタのクライアントな立ち位置となります。

gethue.com

また、Hueはこのところ積極的なアップデートがあるので常に最新を使いたい!が、CDHはその都度追従してくれるわけでも無い状態です。そこで今回は、CDHから切り離して、Hue On Dockerで代わりが出来ないか考えてみます。

Hue自身については、12/23の以下のアドカレでアップデート情報があるので気になる方はご覧になって下さい。

qiita.com

前提

  • CDHを使っている(v6.3.2 を今回の題材にしているが、v5系でも良いはず)
  • Hueを動かすDockerホストには管理者しかリモートログイン出来ず、利用者はWebUIのみとする
  • Cloudera Navigator、Navigator Optimizerの利用は対象外(私が利用できないので検証出来ない為)
  • HTTPS対応は今回対象外とする(検証環境を用意するには時間が足りないので…)
  • LDAPSAML認証については、対象外(検証環境を用意するには時間が足りないので…)
  • HueでつかうSparkについては、Livyが用意出来なかったので見送り
  • Kubernetesでの稼働は見送り(時間足らんかった…)

  • CDHにHueを入れてるけど、最新のHueに上げたくてウズウズしている

Docker でHueを動かしてみる

hue/tools/docker/hue at master · cloudera/huedocker-compose.yml があるのでそちらを参考にします。

また、Dockerfileもあります1 が、 Makefile を読むと分かるように単純にdocker buildするだけではダメそうなので、今回はbuild済みのDockerHubにある コンテナイメージ を使う方向で進めます。

早速、Docker Composeは以下の様にしてます。

version: '3.8'
services:
  hue:
    image: gethue/hue:20201215-135001   # ・・・★1
    hostname: hue
    container_name: hue
    ports:
      - "8888:8888"
    volumes:
      - ./conf/hue/z-hue.ini:/usr/share/hue/desktop/conf/z-hue.ini    # ・・・★2
      - ./conf/hue/log.conf:/usr/share/hue/desktop/conf/log.conf
      - ./conf/hive-conf/:/etc/hive/conf/  # ・・・★3
    depends_on:
    - database
    networks:
      - backend
    extra_hosts:  # ・・・★4
      - "my-cdh-namenode-vip.example.com:192.0.2.1"
      - "my-cdh-hiveserver2.example.com:192.0.2.2"
      - "my-cdh-m01.example.com:192.0.2.3"
      - "my-cdh-m02.example.com:192.0.2.4"
      - "my-cdh-m03.example.com:192.0.2.5"
      - "my-cdh-w01.example.com:192.0.2.6"

# 外部RDBでもOK
  database:  # ・・・★5
    image: mysql:8.0
    hostname: database
    container_name: database
    env_file:
      - ./env/database.env
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --init-connect='SET NAMES UTF8;' --innodb-flush-log-at-trx-commit=0
    volumes:
      - ./db/data:/var/lib/mysql # ・・・★6
      - ./db/sql:/docker-entrypoint-initdb.d  # ・・・★7
#      - ./db/my.cnf:/etc/mysql/conf.d/my.cnf # 必要ならここでチューニングしてください
    networks:
      - backend

networks:
  backend:

※全てのサンプルファイルは全てGithubに置いてます2

起動時の注意

実行する手順は、 初回だけ以下の様にdatabaseだけ先に起動して、初回起動時にHueのデータベースを作成してからHueを起動します。

$ docker-compose up --no-start
$ docker-compose start database
$ docker-compose up -d

2回目以降は、 docker-compose up -d のみでOKです。

では、Docker Composeを見ていきます。

利用するコンテナイメージについて

★1について、hueのイメージは、latest から変更しています。理由は簡単で、HueのDockerhubにあるイメージは定期的に更新しているので latestを使ってしまうと、ある日突然、動かなくなるなんて事にならないように、ある日の場所を固定するようにしています。

ただ、タグが 20201215-135001 とソースのどの時点か分かり難いので、本番で運用するなら、自前でmake使ってbuildする際に、Hueのバージョンとコミットハッシュをタグに仕込むなどの工夫をした方が良さそうです。

この辺は 以下の記事が参考になります。

qiita.com

Hueの設定ファイルの運用について

★2について、ホスト上のファイルをそのまま置き換える様にしています。 初回時は色々と設定をいじるので直接置き換える方が楽なのでこのようにしています。 また、マウントするのは hue.ini ではなく z-hue.ini であるところが注意です。

z-hue.ini はデフォルト値からの変更分だけ設定するたけで良いためです。 hue.ini としてしまうと全ての設定値を入れる必要があるのでとても面倒です。

ただ、このままだと、実行するホストに設定ファイルを持っていくようにするなどの対応がひつようになりますので、取り回しが面倒です。本番運用の際は、変更する設定値は固定されるはずなので、 Hueが起動時に実行するスクリプト にて、envsubstで環境変数で差し替え出来るようにするなどの工夫が必要になる3 と考えます。

z-hue.ini の中身自体は、ここでは言及しませんが、CDHで使っているHueの設定を参考に変更していけばよいです。 また、すべての設定値については、 hue/hue.ini at master · cloudera/hue · GitHub にすべて上がってるのでこちらが参考になります。

また、設定が正しいかについては、docker-composeで出力しているログだけでなく、WebUIの管理画面から以下の様に問題が発生していないかチェックしてください。

f:id:yassan0627:20201221060612p:plain
設定ファイルの確認

★3のHiveの設定ですが、これは、Cloudera ManagerからHiveのクライアント設定をダウンロードしたものを置くようにしています。 また、これは、 hue.iniで hive_conf_dir にて利用しているものです。流石にこれをenvsubstで差し替えするのは苦行すぎるので、イメージに含めるか、 startup.sh にて取得する処理を書くなど工夫がひつようになりそうです(もしくはVolumeコンテナにしてマウントして使うとか)。

/etc/hosts について

★4で追加しているhostsですが、設定ファイルで参照している対象のHadoopクラスタのホスト分だけ追加しています。

Hueから参照する外部DBについて

★5について、今回はComposeにMySQLをふくめていますが、本番運用時には、これまで使ってきたHueのデータベースがあるはずなので、そちらをHueから参照したらよいです。

起動する度に揮発しないように ★6でMySQLのデータ領域をホストから直接マウントして永続化しています。

★7の ./db/sql:/docker-entrypoint-initdb.d についてですが、MySQLを初回起動する際に実行するSQLクエリを置いています。
初回時にHueで使うデータベースを作成する必要があるためです。 ただ、こちらは初回起動時(= /var/lib/mysql が空っぽの場合)のみ実行となります。起動時に毎回実行するクエリじゃないのでお気をつけ下さい。忘れてるとハマリます。

その他の考慮ポイント

Hueの負荷分散について

現行のCDHのHueがLBを使って負荷分散している場合は、 How to Add a Hue Load Balancer | 6.3.x | Cloudera Documentation のとおり、簡単にHueのロール追加してLBからアクセスするだけなので簡単ですが、自前でやるにはその辺りも自分で用意が必要になります。

後、Configure Hue for High Availability | 6.3.x | Cloudera Documentation あたりも。

Hueの監視について

CDHから切り離すということは、自前で監視も必要になります。 Hueからのメール通知は z-hue.ini[desktop] セクションの django_server_emaildjango_email_backend に設定したら良いですが、 Hueのアプリログの監視だったり、リソースの監視も必要です。

Loggingについて

Hueアプリログについては、Hueではfluentdの方法を以下で紹介しています。 Logs - Cloud :: Hue SQL Assistant Documentation

他にも今回の場合は、 Dockerの機能を使ってloggingの設定をするなど考えられます。Syslogに転送とか。 設定方法や詳細については、以下を参考にしてください。

また、長期運用する際、アプリログでホストのDiskを溢れさせないように最低でも、Docker daemonの設定 daemon.json を変更して、最大ファイルサイズや世代管理などを設定するか4 、Docker ComposeファイルのHueサービスに設定するか5して下さい。

Metricsについて

リソース監視については、 以下の通り、[desktop] セクションの enable_prometheus=true とする事でメトリックがPromethus向けのエンドポイント http://hue-server:8888/metrics から取得出来るのでこれを使えば良さそうです。

こんな感じのものが取れます

詳細

# HELP django_db_new_connection_errors_total Counter of connection failures by database and by vendor.
# TYPE django_db_new_connection_errors_total counter
# HELP process_virtual_memory_bytes Virtual memory size in bytes.
# TYPE process_virtual_memory_bytes gauge
process_virtual_memory_bytes 1.896771584e+09
# HELP process_resident_memory_bytes Resident memory size in bytes.
# TYPE process_resident_memory_bytes gauge
process_resident_memory_bytes 2.1581824e+08
# HELP process_start_time_seconds Start time of the process since unix epoch in seconds.
# TYPE process_start_time_seconds gauge
process_start_time_seconds 1.60849601239e+09
# HELP process_cpu_seconds_total Total user and system CPU time spent in seconds.
# TYPE process_cpu_seconds_total counter
process_cpu_seconds_total 85.46000000000001
# HELP process_open_fds Number of open file descriptors.
# TYPE process_open_fds gauge
process_open_fds 19.0
# HELP process_max_fds Maximum number of open file descriptors.
# TYPE process_max_fds gauge
process_max_fds 1.048576e+06
# HELP django_http_ajax_requests_total Count of AJAX requests.
# TYPE django_http_ajax_requests_total counter
django_http_ajax_requests_total 922.0
# HELP django_http_ajax_requests_created Count of AJAX requests.
# TYPE django_http_ajax_requests_created gauge
django_http_ajax_requests_created 1.608496022301734e+09
# HELP django_http_responses_body_total_bytes Histogram of responses by body size.
# TYPE django_http_responses_body_total_bytes histogram
django_http_responses_body_total_bytes_bucket{le="0.0"} 22.0
django_http_responses_body_total_bytes_bucket{le="1.0"} 22.0
django_http_responses_body_total_bytes_bucket{le="2.0"} 22.0
django_http_responses_body_total_bytes_bucket{le="4.0"} 22.0
django_http_responses_body_total_bytes_bucket{le="8.0"} 22.0
django_http_responses_body_total_bytes_bucket{le="16.0"} 739.0
django_http_responses_body_total_bytes_bucket{le="32.0"} 750.0
django_http_responses_body_total_bytes_bucket{le="64.0"} 752.0
django_http_responses_body_total_bytes_bucket{le="128.0"} 755.0
django_http_responses_body_total_bytes_bucket{le="256.0"} 811.0
django_http_responses_body_total_bytes_bucket{le="512.0"} 824.0
django_http_responses_body_total_bytes_bucket{le="1024.0"} 834.0
django_http_responses_body_total_bytes_bucket{le="2048.0"} 843.0
django_http_responses_body_total_bytes_bucket{le="4096.0"} 848.0
django_http_responses_body_total_bytes_bucket{le="8192.0"} 859.0
django_http_responses_body_total_bytes_bucket{le="16384.0"} 864.0
django_http_responses_body_total_bytes_bucket{le="32768.0"} 864.0
django_http_responses_body_total_bytes_bucket{le="65536.0"} 869.0
django_http_responses_body_total_bytes_bucket{le="131072.0"} 885.0
django_http_responses_body_total_bytes_bucket{le="262144.0"} 910.0
django_http_responses_body_total_bytes_bucket{le="524288.0"} 911.0
django_http_responses_body_total_bytes_bucket{le="1.048576e+06"} 911.0
django_http_responses_body_total_bytes_bucket{le="2.097152e+06"} 911.0
django_http_responses_body_total_bytes_bucket{le="4.194304e+06"} 911.0
django_http_responses_body_total_bytes_bucket{le="8.388608e+06"} 911.0
django_http_responses_body_total_bytes_bucket{le="1.6777216e+07"} 911.0
django_http_responses_body_total_bytes_bucket{le="3.3554432e+07"} 911.0
django_http_responses_body_total_bytes_bucket{le="6.7108864e+07"} 911.0
django_http_responses_body_total_bytes_bucket{le="1.34217728e+08"} 911.0
django_http_responses_body_total_bytes_bucket{le="2.68435456e+08"} 911.0
django_http_responses_body_total_bytes_bucket{le="5.36870912e+08"} 911.0
django_http_responses_body_total_bytes_bucket{le="+Inf"} 911.0
django_http_responses_body_total_bytes_count 911.0
django_http_responses_body_total_bytes_sum 6.98808e+06
# HELP django_http_responses_body_total_bytes_created Histogram of responses by body size.
# TYPE django_http_responses_body_total_bytes_created gauge
django_http_responses_body_total_bytes_created 1.608496022302478e+09
# HELP django_http_requests_latency_including_middlewares_seconds Histogram of requests processing time (including middleware processing time).
# TYPE django_http_requests_latency_including_middlewares_seconds histogram
django_http_requests_latency_including_middlewares_seconds_bucket{le="0.005"} 0.0
django_http_requests_latency_including_middlewares_seconds_bucket{le="0.01"} 2.0
django_http_requests_latency_including_middlewares_seconds_bucket{le="0.025"} 18.0
django_http_requests_latency_including_middlewares_seconds_bucket{le="0.05"} 95.0
django_http_requests_latency_including_middlewares_seconds_bucket{le="0.075"} 773.0
django_http_requests_latency_including_middlewares_seconds_bucket{le="0.1"} 878.0
django_http_requests_latency_including_middlewares_seconds_bucket{le="0.25"} 1100.0
django_http_requests_latency_including_middlewares_seconds_bucket{le="0.5"} 1146.0
django_http_requests_latency_including_middlewares_seconds_bucket{le="0.75"} 1166.0
django_http_requests_latency_including_middlewares_seconds_bucket{le="1.0"} 1172.0
django_http_requests_latency_including_middlewares_seconds_bucket{le="2.5"} 1184.0
django_http_requests_latency_including_middlewares_seconds_bucket{le="5.0"} 1184.0
django_http_requests_latency_including_middlewares_seconds_bucket{le="7.5"} 1185.0
django_http_requests_latency_including_middlewares_seconds_bucket{le="10.0"} 1185.0
django_http_requests_latency_including_middlewares_seconds_bucket{le="+Inf"} 1185.0
django_http_requests_latency_including_middlewares_seconds_count 1185.0
django_http_requests_latency_including_middlewares_seconds_sum 145.3799285888672
# HELP django_http_requests_latency_including_middlewares_seconds_created Histogram of requests processing time (including middleware processing time).
# TYPE django_http_requests_latency_including_middlewares_seconds_created gauge
django_http_requests_latency_including_middlewares_seconds_created 1.608496022301204e+09
# HELP django_db_execute_total Counter of executed statements by database and by vendor, including bulk executions.
# TYPE django_db_execute_total counter
django_db_execute_total{alias="default",vendor="mysql"} 4671.0
# HELP django_db_execute_created Counter of executed statements by database and by vendor, including bulk executions.
# TYPE django_db_execute_created gauge
django_db_execute_created{alias="default",vendor="mysql"} 1.608496025350343e+09
# HELP django_db_new_connections_total Counter of created connections by database and by vendor.
# TYPE django_db_new_connections_total counter
django_db_new_connections_total{alias="default",vendor="mysql"} 1234.0
# HELP django_db_new_connections_created Counter of created connections by database and by vendor.
# TYPE django_db_new_connections_created gauge
django_db_new_connections_created{alias="default",vendor="mysql"} 1.608496025331346e+09
# HELP django_db_execute_many_total Counter of executed statements in bulk operations by database and by vendor.
# TYPE django_db_execute_many_total counter
# HELP hue_queries_numbers Hue - numbers of queries
# TYPE hue_queries_numbers gauge
hue_queries_numbers 0.0
# HELP django_http_requests_total_by_method_total Count of requests by method.
# TYPE django_http_requests_total_by_method_total counter
django_http_requests_total_by_method_total{method="GET"} 418.0
django_http_requests_total_by_method_total{method="POST"} 768.0
# HELP django_http_requests_total_by_method_created Count of requests by method.
# TYPE django_http_requests_total_by_method_created gauge
django_http_requests_total_by_method_created{method="GET"} 1.608498263366093e+09
django_http_requests_total_by_method_created{method="POST"} 1.608498266610787e+09
# HELP django_db_errors_total Counter of execution errors by database, vendor and exception type.
# TYPE django_db_errors_total counter
# HELP hue_local_active_users Hue Active Users in Local Instance
# TYPE hue_local_active_users gauge
hue_local_active_users 1.0
# HELP python_info Python platform information
# TYPE python_info gauge
python_info{implementation="CPython",major="2",minor="7",patchlevel="17",version="2.7.17"} 1.0
# HELP django_http_responses_total_by_status_total Count of responses by status.
# TYPE django_http_responses_total_by_status_total counter
django_http_responses_total_by_status_total{status="302"} 22.0
django_http_responses_total_by_status_total{status="404"} 5.0
django_http_responses_total_by_status_total{status="500"} 11.0
django_http_responses_total_by_status_total{status="200"} 1147.0
# HELP django_http_responses_total_by_status_created Count of responses by status.
# TYPE django_http_responses_total_by_status_created gauge
django_http_responses_total_by_status_created{status="302"} 1.608498266638196e+09
django_http_responses_total_by_status_created{status="404"} 1.608500670664722e+09
django_http_responses_total_by_status_created{status="500"} 1.60849828267096e+09
django_http_responses_total_by_status_created{status="200"} 1.608498263373063e+09
# HELP hue_active_users Hue Active Users in All Instances
# TYPE hue_active_users gauge
hue_active_users 1.0

ドキュメントでは以下で紹介しています。 Metrics - Cloud :: Hue SQL Assistant Documentation

また、管理画面でキレイにして確認出来るようになってました。 http://hue-server:8888/hue/desktop/metrics

Tracingについて

Tracingについては、Jeagerを内包しているようなので、こちらも面白そうです(あらやだイケメン)
詳細は Tracing - Cloud :: Hue SQL Assistant Documentation を参照下さい。

さいごに

セキュアにしたい!とか、可用性を十分に担保しないといけないという要件がないのなら、
個人的にはHueをCDHから切り離してDockerつかって運用でも十分いけるんじゃないかなぁと考えました。 また、今回見送った点については、別に出来ないってこともないです。

他にも出来れば、複数のクラスタを扱えるように出来たら良いけど、上手い事出来ないものか🤔ヤリカタワカラン

以上、「Cloudera Hue をCDHから切り離してDockerで運用出来ないか検討してみる」でした。


  1. Dockerfile やPython3版の Dockerfile.py3 が用意されています。

  2. https://github.com/yassan/docker-compose-cloudera-hue

  3. digdagサーバをコンテナで動かしてジョブもコンテナで動かす際の設定例 - Qiitaentrypoint.sh が参考になります。
    差し替え用の設定ファイル( /etc/digdag/server.properties )をイメージに含めておいて、 entrypoint.sh で実行する際にenvsubstで置き換えるイメージです。
    Hueの場合は、差し替え用設定ファイルを /usr/share/hue/z-hue.ini.template 辺りに置いといて、 startup.sh にてenvsubstで z-hue.ini に置き換えます。

  4. Configure the default logging driver - Configure logging drivers | Docker Documentation

  5. 例えば logging - Compose file version 3 reference | Docker Documentation にあるとおり、docker-compose.yml の services.hue.loggingとして追加するとか。