- 投稿日:2019-04-17T23:34:49+09:00
GKEでAirflow環境構築
はじめに
GKE(Kubernetes)とAirflowの勉強もかねて、GKEでAirflowの環境を構築します。
(ここをかなり参考にしてます。)Cloud Composerを使わないのか?
※2019/4/16時点
GCPではAirflowのマネージドサービスであるCloud Composerというサービスがあり、
がっつりComposerでジョブ管理していくのであればぜひComposerを使いたい。
が、スモールスタートするには月数万円とそれなりのコストがかかるので
まずはGKEで半マネージドな感じでやってみます。ちなみに、GKEだとg1-smallで月5千円くらいになります。(GKEは最小3ノード)
f1-microだとさすがにAirflow動かないので、g1-smallが最小インスタンスです。
個人でやってみる場合にはpreemptible(24時間起動後再起動するインスタンス)モードで立ち上げれば月1500円くらいになります。参考:Composerの料金
Cloud Composer Flexible Environment?
Google Cloud Next '19でComposerについても少し触れられており、
そのうちフレキシブルな環境構築ができるようになるか?実行環境
諸々環境作るのが面倒なので、Cloud Shell使う前提です。
Airflowの構成
Airflowの構成を確認しつつ、GKE + GCPの各種サービスで以下のような構成を作る。
①Webserver
WebUIを提供するPod。②Scheduler
ジョブスケジュールに関する制御を行うPod。ちなみにSchedulerのプロセスが動いてないと手動実行もできない(ずっとno statusになる)。③Worker
実際に処理を実行するプロセス。CeleryExcecuterを使用してWorkerを増やすことで処理の分散ができる。CeleryExcecuter以外のスケールの方法は公式ドキュメント参照④Database
ジョブの情報やスケジュールの情報を格納。GCEのディスクで永続化。GCEのディスクで永続化すると、ReadWriteOnce となり、1つのノードからしかマウントできない。statefulsetにしておけば、常に同じノードで立ち上がるので、Pod再立ち上げの際にマウントエラーが起きない。
また、各Podからアクセスがくるのでクラスタ内部でアクセスできるようにClusterIPを紐付ける。⑤Redis
ジョブ実行状態を保持するためのミドルウェア。RabbitMQ等でも良いらしい。ちなみにComposerはRedis使用。
こちらも各Podからアクセスがくるのでクラスタ内部でアクセスできるようにClusterIPを紐付ける。⑥Service(NodePort)とIngress
外からアクセスできるように、Service(NodePort)とIngressを設定する。
アクセスを制御したい場合は、ロードバランサに対してCloud ArmorでIP制限する等対応する。⑦GCS(Logs)
ログは各種Podから書き込まれてくるため、GCSに設定。⑧SouceRepositories(Dags)
AirflowはDagfile(Python)により実行ジョブの情報を管理する。
バージョン管理もできるので、SourceRepositoriesを使用。参考:Composerの構成
諸々のファイル類
GKEへデプロイするための、諸々のファイル類は以下の通り。
githubairflow-gke
├config
|| airflow.cfg
├keyfile
|| サービスアカウントのkeyfile(〜.json)
├script
|| entrypoint.sh
| Dockerfile
| airflow_deploy.yaml
| cloudbuild.yaml
| ingress_airflow.yamlDockerfile
「set your environment」の部分のENVは使用する自分の環境の情報を記載する
FROM python:3.6 ENV AIRFLOW_GPL_UNIDECODE=yes ENV CONFIG /root/.config/gcloud ENV PATH /root/google-cloud-sdk/bin:$PATH ENV AIRFLOW_HOME /airflow ENV AIRFLOW_CONFIG $AIRFLOW_HOME/airflow.cfg ENV AIRFLOW_USER airflow #---set your environment ENV GCPKEY= ENV GCPSERVICEACCOUNT= ENV GCPPROJECT= ENV DAG_REPOSITORY= ENV AIRFLOW_LOGIN_USER= ENV AIRFLOW_LOGIN_PASS= ENV AIRFLOW_FIRSTNAME= ENV AIRFLOW_LASTNAME= ENV AIRFLOW_EMAIL= #--- RUN apt update \ && apt-get -y install \ sudo \ lhasa \ vim \ curl \ nmap \ netcat\ && pip install \ redis \ celery \ psycopg2 \ apache-airflow \ apache-airflow[postgres] \ apache-airflow[celery] \ apache-airflow[gcp_api] \ flask_bcrypt \ google-api-python-client \ pandas_gbq\ && useradd -ms /bin/bash -d ${AIRFLOW_HOME} ${AIRFLOW_USER} \ && curl https://sdk.cloud.google.com | bash ADD config/airflow.cfg ${AIRFLOW_HOME} ADD keyfile/${GCPKEY} ${CONFIG}/${GCPKEY} ADD script/entrypoint.sh ${AIRFLOW_HOME} WORKDIR ${AIRFLOW_HOME} RUN chmod -R 755 ${AIRFLOW_HOME} RUN chown -R ${AIRFLOW_USER}: ${AIRFLOW_HOME} RUN chown -R ${AIRFLOW_USER}: /root RUN echo "%${AIRFLOW_USER} ALL=NOPASSWD: ALL" >> /etc/sudoers USER ${AIRFLOW_USER} ENTRYPOINT ["./entrypoint.sh"]airflow_deploy.yaml
このファイルでDeploymentやらServiceやらまとめて記載している。
Ingressは稼働させておくと料金かかるので、必要なときだけ作成するために別ファイルで管理。webserverとschedulerとworkerのDockerImageに関しては、コンテナレジストリにPushしたイメージIDを上書きする。
apiVersion : v1 kind : Service metadata : name : service-airflow spec : # type : LoadBalancer type : NodePort ports : - port : 8080 targetPort : 8080 selector : app: airflow-webserver --- apiVersion: v1 kind: Service metadata: name: postgres spec: type: ClusterIP selector: app: postgres ports: - name: postgres protocol: TCP port: 5432 targetPort: postgres --- apiVersion: v1 kind: Service metadata: name: redis spec: type: ClusterIP selector: app: redis ports: - name: redis protocol: TCP port: 6379 targetPort: redis --- apiVersion: apps/v1 kind: StatefulSet metadata: name: postgres spec: serviceName : postgres replicas: 1 selector : matchLabels : app : postgres template: metadata: labels: app: postgres spec: containers: - name: postgres image: postgres ports: - name: postgres containerPort: 5432 env: - name: POSTGRES_USER value: "airflow" - name: POSTGRES_PASSWORD value: "airflow" - name: POSTGRES_DB value: "airflow" - name: PGDATA value: "/var/lib/postgresql/data/pgdata" volumeMounts : - name : volume-postgres mountPath : /var/lib/postgresql/data volumeClaimTemplates: - metadata : name : volume-postgres spec : accessModes: [ "ReadWriteOnce" ] resources: requests: storage: 10Gi --- apiVersion: extensions/v1beta1 kind: Deployment metadata: name: redis spec: replicas: 1 selector : matchLabels : app : redis template: metadata: labels: app: redis spec: containers: - name: redis image: redis ports: - name: redis containerPort: 6379 --- apiVersion: extensions/v1beta1 kind: Deployment metadata: name: airflow-webserver labels : app : airflow-webserver spec: replicas: 1 template : metadata : labels : app : airflow-webserver spec : containers: - name: airflow-webserver image: #container image imagePullPolicy : Always ports : - containerPort : 8080 args : ["webserver"] readinessProbe: httpGet: path: /login/ port: 8080 --- apiVersion: extensions/v1beta1 kind: Deployment metadata: name: airflow-scheduler spec: replicas: 1 template : metadata : labels : app : airflow-scheduler spec : containers: - name: airflow-scheduler image: #container image imagePullPolicy : Always args : ["scheduler"] --- apiVersion: extensions/v1beta1 kind: Deployment metadata: name: airflow-worker labels : app : airflow-worker spec: replicas: 1 template : metadata : labels : app : airflow-worker spec : containers: - name: airflow-worker image: #container image imagePullPolicy : Always args : ["worker"]個人的ハマりポイント
postgresqlの永続化
postgresqlのstatefulsetのENVでPGDATAを「/var/lib/postgresql/data/pgdata」にした上で
「/var/lib/postgresql/data」をマウントしないとエラーでるか、Pod再作成されたときにリセットされてしまう。
参考Ingressのヘルスチェック
ヘルスチェックしにいくページのステータスが200で返ってこないとヘルスチェックがエラーになる。
デフォルトは「/」をチェックしにいくが、Airflowでユーザー認証設定してる場合は、リダイレクトが発生しヘルスチェックは「/」のままだとエラーになるので、こちら参考にヘルスチェック先を変更する。
どの時点でステータス200になるかは、こちらでwebserverのserviceを一時的にLoadBalancerタイプにして確認する。ClusterIPの紐付け
当たり前といえば当たり前だが、各Podとclusterip serviceのlabelを合わせる必要ある
。これ合ってないとservice自体は立ち上がるが、kubectl describe serviceしたときにエンドポイントがnoneになってて結局Pod間通信できない。
色々なWebサイト参考にしながらやるとこの辺が結構不整合起きる。。Workerの起動
ルートユーザーでWorker起動させようとすると以下メッセージでる。
Running a worker with superuser privileges when the
worker accepts messages serialized with pickle is a very bad idea!要はルート権限を持つユーザーでWorker起動するなということみたいなので、
airflowというユーザーを作って、そのユーザーで起動する。ingress_airflow.yaml
apiVersion: extensions/v1beta1 kind: Ingress metadata: name: airflow-ingress namespace: default annotations: kubernetes.io/ingress.global-static-ip-name: "airflow-ingress-ip" # optional spec: backend: serviceName: service-airflow servicePort: 8080cloudbuild.yaml
Cloud Buildを使用する場合には作成する。
個人的にはかなり便利だった。
Source RepositoriesへのpushをトリガーにしてDockerイメージの作成、Container Registryへのpush、Podの再作成など一連の作業を自動化できる。
もちろんトリガーだけでなく、手動でCloud Build実行できる。steps: - name: 'gcr.io/cloud-builders/docker' args: ['build', '-t', '$_CONTAINER_IMG', '$_FILE_PATH'] - name: 'gcr.io/cloud-builders/gcloud' args: ['auth', 'configure-docker'] - name: 'gcr.io/cloud-builders/docker' args: ['push', '$_CONTAINER_IMG'] - name: 'gcr.io/cloud-builders/kubectl' args: ['delete', '-f', '$_FILE_PATH/$_AIRFLOW_YAML'] env: - 'CLOUDSDK_COMPUTE_ZONE=$_ZONE' - 'CLOUDSDK_CONTAINER_CLUSTER=$_CLUSTER_NAME' - name: 'gcr.io/cloud-builders/kubectl' args: ['apply', '-f', '$_FILE_PATH/$_AIRFLOW_YAML'] env: - 'CLOUDSDK_COMPUTE_ZONE=$_ZONE' - 'CLOUDSDK_CONTAINER_CLUSTER=$_CLUSTER_NAME'個人的ハマりポイント
ResponseError: code=403, message=EXTERNAL: Required "container.clusters.get" permission(s) for ~
が出るときは、CloudBuildのサービスアカウントにGKEの管理者の権限を追加するscript
entrypoint.sh
Podが作成されるタイミングで実行されるshellファイル。
やってることは以下。
- AirflowのGCPコネクションを作成する
- postgresql、redisの起動を待つ
- webserverの場合はinitdb(upgradedb)したあとに、airflowのログインユーザー作成する
- dagリポジトリのクローン
- 各サービスの起動
connecitonsを作成する部分の{keyfilename}と{gcp_projectid}はそれぞれ、サービスアカウントのkeyfile名とgcpのプロジェクトIDに置き換える。
#!/usr/bin/env bash CMD="airflow" TRY_LOOP="${TRY_LOOP:-10}" POSTGRES_HOST="${POSTGRES_HOST:-postgres}" POSTGRES_PORT=5432 REDIS_HOST="${REDIS_HOST:-redis}" REDIS_PORT=6379 echo "Postgres host: $POSTGRES_HOST" echo "Redis host: $REDIS_HOST" # set gcp connections $CMD connections --add --conn_id=airflow_gcp --conn_type=google_cloud_platform --conn_extra='{"extra__google_cloud_platform__key_path":"/root/.config/gcloud/{keyfilename}","extra__google_cloud_platform__project":"{gcp_projectid}","extra__google_cloud_platform__scope":"https://www.googleapis.com/auth/cloud-platform"}' # wait for postgres if [ "$1" = "webserver" ] || [ "$1" = "worker" ] || [ "$1" = "scheduler" ] ; then i=0 while [ `sudo nping --tcp $POSTGRES_HOST -p $POSTGRES_PORT | grep -i Rcvd: | sed s/^.*Rcvd:// | sed s/\(.*// | tr -d ' '` -eq 0 ] do i=`expr $i + 1` if [ $i -ge $TRY_LOOP ]; then echo "$(date) - ${POSTGRES_HOST}:${POSTGRES_PORT} still not reachable, giving up" exit 1 fi echo "$(date) - waiting for ${POSTGRES_HOST}:${POSTGRES_PORT}... $i/$TRY_LOOP" sleep 5 done # initdb and register user if [ "$1" = "webserver" ]; then echo "Initialize database..." $CMD upgradedb echo "Register user..." $CMD create_user -r Admin -u ${AIRFLOW_LOGIN_USER} -p ${AIRFLOW_LOGIN_PASS} -f ${AIRFLOW_FIRSTNAME} -l ${AIRFLOW_LASTNAME} -e ${AIRFLOW_EMAIL} fi fi # wait for redis if [ "$1" = "webserver" ] || [ "$1" = "worker" ] || [ "$1" = "scheduler" ] ; then j=0 while [ `sudo nping --tcp $REDIS_HOST -p $REDIS_PORT | grep -i Rcvd: | sed s/^.*Rcvd:// | sed s/\(.*// | tr -d ' '` -eq 0 ] do j=`expr $j + 1` if [ $j -ge $TRY_LOOP ]; then echo "$(date) - ${REDIS_HOST}:${REDIS_PORT} still not reachable, giving up" exit 1 fi echo "$(date) - waiting for ${REDIS_HOST}:${REDIS_PORT}... $j/$TRY_LOOP" sleep 5 done fi #git clone dags gcloud auth activate-service-account ${GCPSERVICEACCOUNT} --key-file $CONFIG/${GCPKEY} --project ${GCPPROJECT} gcloud source repos clone ${DAG_REPOSITORY} --project=${GCPPROJECT} #execute args $CMD "$@"個人的ハマりポイント
source repositories Request had insufficient authentication scopes.
↑のメッセージが出てくるときは、サービスアカウントにsource repositories への権限を付与する。config
airflow.cfg
airflowに関するconfigファイル。
かなり長いので重要なところ(デフォルト修正したところ、自分の環境情報を設定するところ)抜粋。ディレクトリの指定、GCSの指定
[core] # The home folder for airflow, default is ~/airflow airflow_home = /airflow #ホームディレクトリ # The folder where your airflow pipelines live, most likely a # subfolder in a code repository # This path must be absolute dags_folder = /airflow/dags #Dagのディレクトリ # The folder where airflow should store its log files # This path must be absolute base_log_folder = /airflow/logs #logのディレクトリ # Airflow can store logs remotely in AWS S3, Google Cloud Storage or Elastic Search. # Users must supply an Airflow connection id that provides access to the storage # location. If remote_logging is set to true, see UPDATING.md for additional # configuration requirements. remote_logging = True #ログをGCSに置くのでTrue remote_log_conn_id = airflow_gcp #GCPへのコネクションID entrypoint.shにて設定 remote_base_log_folder = gs:// #ログを出力するgcsバケット encrypt_s3_logs = Falseexecutorの指定、DBへの接続情報
# The executor class that airflow should use. Choices include # SequentialExecutor, LocalExecutor, CeleryExecutor, DaskExecutor executor = CeleryExecutor #デフォルトはSequentialExecutor(WebUIから特定ジョブのリランできない) # The SqlAlchemy connection string to the metadata database. # SqlAlchemy supports many different database engine, more information # their website sql_alchemy_conn = postgresql+psycopg2://airflow:airflow@postgres:5432/airflow #posgresへの接続情報 デフォルトはSQLiteになってる(CeleryExecutor使えない)Airflowのユーザー認証
# Set to true to turn on authentication: # https://airflow.incubator.apache.org/security.html#web-authentication authenticate = True #デフォルトはFalse auth_backend = airflow.contrib.auth.backends.password_authAdmin権限のユーザーを作成するための設定
# Use FAB-based webserver with RBAC feature rbac = True #デフォルトはFalseエラー時のメール送信元設定
[smtp] # If you want airflow to send emails on retries, failure, and you want to use # the airflow.utils.email.send_email_smtp function, you have to configure an # smtp server here smtp_host = smtp.gmail.com #gmailのsmtpサーバ smtp_starttls = True smtp_ssl = False smtp_user = #ログインユーザ smtp_password = #ログインパス(アプリパスワードなので注意!) # Uncomment and set the user/pass settings if you want to use SMTP AUTH # smtp_user = airflow # smtp_password = airflow smtp_port = 587 #gmailのsmtpサーバのポート smtp_mail_from = #送信元メールアドレスCeleryのbroker(Redis)と実行結果先(postgresql)の接続情報
# The Celery broker URL. Celery supports RabbitMQ, Redis and experimentally # a sqlalchemy database. Refer to the Celery documentation for more # information. # http://docs.celeryproject.org/en/latest/userguide/configuration.html#broker-settings broker_url = redis://redis:6379/1 # The Celery result_backend. When a job finishes, it needs to update the # metadata of the job. Therefore it will post a message on a message bus, # or insert it into a database (depending of the backend) # This status is used by the scheduler to update the state of the task # The use of a database is highly recommended # http://docs.celeryproject.org/en/latest/userguide/configuration.html#task-result-backend-settings result_backend = db+postgresql://airflow:airflow@postgres:5432/airflowkeyfile
GCPのサービスアカウントのkeyファイル(〜.json)を配置する用のディレクトリ。
GKEへデプロイ
GKEクラスタ立ち上げ
「airflow-gke」というクラスタを立ち上げる
以下は、g1-smallの10GBディスクでus-central1-aゾーンに3ノード構成でクラスタを作成するサンプルコマンド。サンプルgcloud container clusters create airflow-gke --machine-type=g1-small --num-nodes=3 --disk-size=10 --zone=us-central1-apreemptibleにする場合は、「--preemptible」オプションを追加すればよい
kubectlの認証情報の設定
kubernetesクラスタは基本的に「kubectl」コマンドで操作するが、
どのクラスタに対してのコマンド発行なのかを認証するgcloud container clusters get-credentials airflow-gke --zone=us-central1-aリモートリポジトリのclone
任意のディレクトリで以下実行
git clone https://github.com/yakamazu/airflow-gke.gitSource RepositoriesでDag用のリポジトリを作成
詳細割愛
任意のDag用のリポジトリを作成する。ログ用のGCSバケットを作成
詳細割愛
任意のログ用バケットを作成する。サービスアカウントの作成、keyfileの配置
詳細割愛
keyfileはgcsにアップしてgsutil使ってcloudshell環境に持っていく等。Dockerfileの修正
---set your environmentの部分
ENV GCPKEY:サービスアカウントのキーファイル名
ENV GCPSERVICEACCOUNT:サービスアカウント名
ENV GCPPROJECT:GCPプロジェクトID
ENV DAG_REPOSITORY:SourceRepositoriesのDAGのリポジトリ名
ENV AIRFLOW_LOGIN_USER:AirflowのWebUIログインユーザー
ENV AIRFLOW_LOGIN_PASS:AirflowのWebUIログインパスワード
ENV AIRFLOW_FIRSTNAME:AirflowのWebUIログインユーザー表示名(名)
ENV AIRFLOW_LASTNAME:AirflowのWebUIログインユーザー表示名(姓)
ENV AIRFLOW_EMAIL:AirflowのWebUIログインユーザーのメールアドレスconfigファイルの修正
諸々のファイル類の部分で記載したconfigを修正する。
特にログのGCSの場所と、メール情報。entrypoint.shの修正
connecitonsを作成する部分の{keyfilename}と{gcp_projectid}はそれぞれ、サービスアカウントのkeyfile名とgcpのプロジェクトIDに置き換える。
$CMD connections --add --conn_id=airflow_gcp --conn_type=google_cloud_platform --conn_extra='{"extra__google_cloud_platform__key_path":"/root/.config/gcloud/{keyfilename}","extra__google_cloud_platform__project":"{gcp_projectid}","extra__google_cloud_platform__scope":"https://www.googleapis.com/auth/cloud-platform"}'Dockerイメージの作成
Dockerfileが格納されているディレクトリで以下コマンド実行。
airflow-gkeというイメージが作成される。docker image build -t asia.gcr.io/プロジェクトID/airflow-gke:latest .Container RegistryへのPush
docker push 使うための認証
gcloud auth configure-dockerContainer RegistryへのPush
docker push asia.gcr.io/プロジェクトID/airflow-gke:latestairflow_deploy.yamlの修正
deploymentのwebserverとschedulerとworkerに関しては、コンテナレジストリにPushしたイメージIDを上書きする。
「image」の#container imageのところを「asia.gcr.io/プロジェクトID/airflow-gke:latest」にする。airflow_deploy.yamlのapply
以下コマンドの実行
kubectl apply -f airflow_deploy.yamlIngressに固定IPを設定する
こちら参考にingress_airflow.yamlのglobal-static-ip-nameと名前を合わせた固定IPを作成する。
gcloud compute addresses create airflow-ingress-ip --globalingress_airflow.yamlのapply
以下コマンドの実行
※起動するとロードバランサー立ち上がり、料金かかるkubectl apply -f ingress_airflow.yaml起動確認
Podの一覧
1/1のRunningになってればOK
$ kubectl get pod NAME READY STATUS RESTARTS AGE airflow-scheduler-7bc759f895-vvssx 1/1 Running 0 13m airflow-webserver-6bd76d6d6b-lwzhw 1/1 Running 0 13m airflow-worker-59d954659d-v2bb4 1/1 Running 0 13m postgres-0 1/1 Running 0 12m redis-56c6759df8-clfqj 1/1 Running 0 13mServiceの一覧
postgresとredisとservice-airflowが立ち上がってればOK
$ kubectl get service NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.3.240.1 <none> 443/TCP 9h postgres ClusterIP 10.3.254.25 <none> 5432/TCP 14m redis ClusterIP 10.3.249.32 <none> 6379/TCP 14m service-airflow NodePort 10.3.243.40 <none> 8080:30957/TCP 14mIngressの一覧
airflow-ingressが立ち上がっていて、作成した外部IP(airflow-ingress-ip)が割り当てられていればOK
IPが割り当てられてからアクセス可能になるまでは若干タイムラグあり。$ kubectl get ingress NAME HOSTS ADDRESS PORTS AGE airflow-ingress * ~.~.~.~ 80 5hDAGの登録、反映
DAGをSourceRepositoriesにPushし、Airflowに更新を反映させるには、
サービスを再起動させる必要がある。
各Podでサービス再起動してもよいが面倒なので、ingress_airflow.yamlをdeleteしてからapplyする。kubectl delete -f airflow_deploy.yaml kubectl apply -f airflow_deploy.yamlWebUIログイン
IngressのIPにアクセスし、Dockerfileで設定したAirflowログインユーザー名、パスワードを入力する。
※sampleのDagファイルは自分で予め作ってSource RepositoriesにPushしておく。
ジョブ実行
エラー時メール配信
エラーになったらこんな感じでメール届く。
ジョブリラン
GCSにログ出力
指定したバケットにDAG単位でフォルダできる
DAGフォルダの中はタスクID単位でのフォルダができる
タスクIDフォルダの中に実行時間ごとのフォルダができる
実行時間フォルダの中にログ出力される
(おまけ)よく使うkubectlコマンド
kubectl get pod -o wide
podの一覧を表示する。
-o wideをつけると、IPやどのNodeで立ち上がってるかの情報も出力される。kubectl exec -it pod名 /bin/bash
podの中に入るイメージ。
kubectl get service
serviceの一覧を表示する。IPを確認できる。
kubectl get ingress
ingressの一覧を表示する。IPを確認できる。
kubectl get pvc
pvc(永続ボリューム)の一覧を表示する。
kubectl describe pod pod名
podの詳細情報、起動時のメッセージを確認できる。
kubectl describe service service名
serviceの詳細情報を確認できる。
kubectl logs pod名
podの標準出力を確認する。
kubectl apply -f yamlファイル
yamlファイルの内容をデプロイする
kubectl delete -f yamlファイル
yamlファイルの内容を削除する
- 投稿日:2019-04-17T22:33:03+09:00
Docker でよく使うコマンド 備忘録
- 投稿日:2019-04-17T22:28:38+09:00
An example of #markdown #HTML conversion from standard input / output using #ruker gem's redcarpet / reverse_markdown with #docker
Dockerfile
FROM ruby RUN gem install reverse_markdown redcarpetEXE
docker build . -t ruby-gems$ echo '<h1>hey</h1>' | docker run -i ruby-gems reverse_markdown # hey $ echo "abc" | docker run -i ruby-gems redcarpet <p>abc</p># Maybe badcase
$ docker run ruby-gems /bin/bash -c "echo '<h1>head</h1>' | reverse_markdown" # head $ docker run ruby-gems /bin/bash -c "echo '# h1' | redcarpet" <h1>h1</h1>ref
Original by Github issue
- 投稿日:2019-04-17T22:05:19+09:00
docker network macvlan機能 小ネタ
まえがき
あそびでoracle rac環境をdocker上で構築しようとする過程で、macvlan機能と遭遇したので、普通のnetworkとの違いなんだろっておもっていじっていたら、違いを一つ見つけたので、その備忘録。
みつけた違い
docker hostのNICと同じサブネットをdocker networkに定義した際、macvlanの機能を使用した場合はdocker hostからping -c 4 8.8.8.8を投げても疎通できたが、macvlanの機能を使用していない場合は、疎通できなかった。へぇーという感じ。コンテナとdocker hostで同じサブネットを定義したいときはmacvlan機能を使用しないとdocker hostから外へ出れないんだなって感じかな。
検証
デフぉ
[oracle@centos ~]$ docker network ls NETWORK ID NAME DRIVER SCOPE f5d864e89835 bridge bridge local ad2f99e1e398 host host local 4d1d8a2fc9da none null localdocker hostのNIC確認
[oracle@centos ~]$ ifconfig eth0 eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 inet 192.168.1.109 netmask 255.255.255.0 broadcast 192.168.1.255 inet6 fe80::9cc1:bb9:1899:68ad prefixlen 64 scopeid 0x20<link> ether 00:d8:61:2c:f1:5b txqueuelen 1000 (Ethernet) RX packets 115629 bytes 147259740 (140.4 MiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 82650 bytes 11637575 (11.0 MiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 device interrupt 16 memory 0xa1200000-a1220000macvlanを使ってdocker network作成し、pingなげる
[oracle@centos ~]$ docker network create -d macvlan --subnet=192.168.1.0/24 mcv f04e5a5e8a881f7a8508471a8a955af51693d0df5c480d6aac67720f96858994 [oracle@centos ~]$ docker network ls NETWORK ID NAME DRIVER SCOPE f5d864e89835 bridge bridge local ad2f99e1e398 host host local f04e5a5e8a88 mcv macvlan local 4d1d8a2fc9da none null local [oracle@centos ~]$ ping -c 4 8.8.8.8 PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data. 64 bytes from 8.8.8.8: icmp_seq=1 ttl=119 time=6.70 ms 64 bytes from 8.8.8.8: icmp_seq=2 ttl=119 time=7.71 ms 64 bytes from 8.8.8.8: icmp_seq=3 ttl=119 time=13.2 ms 64 bytes from 8.8.8.8: icmp_seq=4 ttl=119 time=5.33 ms --- 8.8.8.8 ping statistics --- 4 packets transmitted, 4 received, 0% packet loss, time 3005ms rtt min/avg/max/mdev = 5.335/8.242/13.213/2.993 msmacvlanを使わずdocker network作成し、pingなげる。直後すぎるとまだ疎通できるかもしれないが、30秒位したら、疎通できなくなることが確かめられた。
[oracle@centos ~]$ docker network rm mcv mcv [oracle@centos ~]$ docker network ls NETWORK ID NAME DRIVER SCOPE f5d864e89835 bridge bridge local ad2f99e1e398 host host local 4d1d8a2fc9da none null local [oracle@centos ~]$ docker network create --subnet=192.168.1.0/24 non_mcv f869e1c1e3122c239d9f903cb8a9126db5bf9988e72244602e9d88035798a74f [oracle@centos ~]$ docker network ls NETWORK ID NAME DRIVER SCOPE f5d864e89835 bridge bridge local ad2f99e1e398 host host local f869e1c1e312 non_mcv bridge local 4d1d8a2fc9da none null local [oracle@centos ~]$ ping -c 4 8.8.8.8 PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data. --- 8.8.8.8 ping statistics --- 4 packets transmitted, 0 received, 100% packet loss, time 2998msdocker network 消すと元通り疎通できるようになる。
[oracle@centos ~]$ docker network rm non_mcv non_mcv [oracle@centos ~]$ ping -c 4 8.8.8.8 PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data. 64 bytes from 8.8.8.8: icmp_seq=1 ttl=119 time=5.74 ms 64 bytes from 8.8.8.8: icmp_seq=2 ttl=119 time=6.06 ms 64 bytes from 8.8.8.8: icmp_seq=3 ttl=119 time=8.41 ms 64 bytes from 8.8.8.8: icmp_seq=4 ttl=119 time=6.52 ms --- 8.8.8.8 ping statistics --- 4 packets transmitted, 4 received, 0% packet loss, time 3004ms rtt min/avg/max/mdev = 5.749/6.688/8.418/1.037 msちなみにnon_mcvの場合もmcvの場合もコンテナから外に出て行くことができなかった。nat変換うんぬんは無効になる模様。。セグメントはちゃんと切り分ける必要があるってことかなー。参考文献のほうにそんなかんじのことが書いてある。
[oracle@centos ~]$ docker run -it --name test3 --net=non_mcv -d oraclelinux:7-slim /bin/bash bd12acc165e73f163bd00347361805bade3cb7a649d205b9725d508fbd765910 [oracle@centos ~]$ docker exec -it test3 /bin/bash bash-4.2# yum -y install iputils Loaded plugins: ovl https://yum.oracle.com/repo/OracleLinux/OL7/UEKR5/x86_64/repodata/repomd.xml: [Errno 14] curl#6 - "Could not resolve host: yum.oracle.com; Unknown error" Trying other mirror. One of the configured repositories failed (Latest Unbreakable Enterprise Kernel Release 5 for Oracle Linux 7Server (x86_64)), and yum doesn't have enough cached data to continue. At this point the only safe thing yum can do is fail. There are a few ways to work "fix" this: 1. Contact the upstream for the repository and get them to fix the problem. 2. Reconfigure the baseurl/etc. for the repository, to point to a working upstream. This is most often useful if you are using a newer distribution release than is supported by the repository (and the packages for the previous distribution release still work). 3. Run the command with the repository temporarily disabled yum --disablerepo=ol7_UEKR5 ... 4. Disable the repository permanently, so yum won't use it by default. Yum will then just ignore the repository until you permanently enable it again or use --enablerepo for temporary usage: yum-config-manager --disable ol7_UEKR5 or subscription-manager repos --disable=ol7_UEKR5 5. Configure the failing repository to be skipped, if it is unavailable. Note that yum will try to contact the repo. when it runs most commands, so will have to try and fail each time (and thus. yum will be be much slower). If it is a very temporary problem though, this is often a nice compromise: yum-config-manager --save --setopt=ol7_UEKR5.skip_if_unavailable=true failure: repodata/repomd.xml from ol7_UEKR5: [Errno 256] No more mirrors to try. https://yum.oracle.com/repo/OracleLinux/OL7/UEKR5/x86_64/repodata/repomd.xml: [Errno 14] curl#6 - "Could not resolve host: yum.oracle.com; Unknown error" [oracle@centos ~]$ docker run -it --name test3 --net=mcv -d oraclelinux:7-slim /bin/bash [oracle@centos ~]$ docker exec -it test3 /bin/bash bash-4.2# yum -y install iputils Loaded plugins: ovl https://yum.oracle.com/repo/OracleLinux/OL7/UEKR5/x86_64/repodata/repomd.xml: [Errno 14] curl#6 - "Could not resolve host: yum.oracle.com; Unknown error" Trying other mirror. One of the configured repositories failed (Latest Unbreakable Enterprise Kernel Release 5 for Oracle Linux 7Server (x86_64)), and yum doesn't have enough cached data to continue. At this point the only safe thing yum can do is fail. There are a few ways to work "fix" this: 1. Contact the upstream for the repository and get them to fix the problem. 2. Reconfigure the baseurl/etc. for the repository, to point to a working upstream. This is most often useful if you are using a newer distribution release than is supported by the repository (and the packages for the previous distribution release still work). 3. Run the command with the repository temporarily disabled yum --disablerepo=ol7_UEKR5 ... 4. Disable the repository permanently, so yum won't use it by default. Yum will then just ignore the repository until you permanently enable it again or use --enablerepo for temporary usage: yum-config-manager --disable ol7_UEKR5 or subscription-manager repos --disable=ol7_UEKR5 5. Configure the failing repository to be skipped, if it is unavailable. Note that yum will try to contact the repo. when it runs most commands, so will have to try and fail each time (and thus. yum will be be much slower). If it is a very temporary problem though, this is often a nice compromise: yum-config-manager --save --setopt=ol7_UEKR5.skip_if_unavailable=true failure: repodata/repomd.xml from ol7_UEKR5: [Errno 256] No more mirrors to try. https://yum.oracle.com/repo/OracleLinux/OL7/UEKR5/x86_64/repodata/repomd.xml: [Errno 14] curl#6 - "Could not resolve host: yum.oracle.com; Unknown error"あとがき
ネットワークもっと掘り下げてみよー。以下の参考文献ではもっと実践的なことをしていて、楽しそう!以上、ありがとうがざいました。
参考文献
Dockerコンテナを外部ネットワークとVLAN Tag接続する
物理のトランクリンクをOpenvSwitchで受けて各Dockerコンテナに渡す
Dockerのマルチホストネットワークで複数ホスト間を繋ぐ仮想ネットワークを作る(Dockerの最新機能を使ってみよう:第1回)
- 投稿日:2019-04-17T18:31:43+09:00
ContainerRegistry(GCR)に登録されたDockerイメージをGCE上で動かす
ContainerRegistryに登録したDockerイメージをGCE上で動かしたい!
※以前書いた下記記事の続きです
・ローカルからContainerRegistry(GCR)にDockerイメージをpushする
https://qiita.com/issei_0403/items/c27b2c30adf0455a6218・werckerのdocker-pushでDockerイメージをContainer Registry(GCR)に登録する
https://qiita.com/issei_0403/items/d2197a7756cbd1de5b49............ContainerRegistry(GCR)に登録されているDockerイメージをGCEに乗っけてJavaのバッチ処理を動かしたいです!!!!!!!
というわけで本題に入ります。
GCEインスタンス作成
何はともあれインスタンスを作りましょう。
『このVMインスタンスにコンテナイメージをデプロイする。』にチェックをつけ、ブートディスクの種類はContainer-Optimized OSを選択します。
コンテナイメージについては、GCRの画面上で確認して入力してください。『gcr.io/【プロジェクトID】/イメージ名』って感じです。
サービスアカウントについては、本インスタンスから他サービスに対する権限がないとダメなので、適宜設定してください。私の場合DB接続したりするので、下記の通り設定しました。
cloud-initの設定
GCEインスタンス上にのせたDockerコンテナがどういう挙動をするかをここで設定します。
key、valueの組み合わせで設定します。
具体的には、、、google-logging-enabled
trueuser-data
#cloud-config users: - name: cos-user runcmd: - sudo usermod -aG docker cos-user - sudo -u cos-user docker-credential-gcr configure-docker - sudo -u cos-user docker run -it gcr.io/【プロジェクトID】/【イメージ】 > /var/log/cloud-init.log - shutdown -h nowgce-container-declaration
containers: - name: 【イメージ】 image: gcr.io/【プロジェクトID】/【イメージ】 stdin: false tty: false restartPolicy: Never # This container declaration format is not public API and may change without notice. Please # use gcloud command-line tool or Google Cloud Console to run Containers on Google Compute Engine.※今回は一度切り動いてくれればいいbatch的な処理なので、restartPolicy: Neverとしています。Alwaysにすると、Dockerコンテナのプロセスが終了→起動を繰り返すような動きになります。
ほんとに動いてるか確認する
インスタンス一覧のSSHをクリックしてインスタンス内に入ります。
$docker ps -aでcontainer idを確認して、
$docker logs 【container id】でログが見れるはずです。
また、/var/log/cloud-init.logにログを出力するよう設定しているので、
インスタンス一覧の右側にあるボタン→ログを表示でstack driverでも確認できます。
- 投稿日:2019-04-17T17:15:19+09:00
タイムラプス動画をffmpegで作る
TL;DR
- タイムラプス動画(特定の時間間隔で抜き出した画像でつくるコマ落ち動画)を,ffmpegで作る方法を紹介します.
- ffmpegは,GPU(NVEnc)対応FFmpegのポータブルなDockerfileで作ったコンテナベースのコマンドで実行します(GPUが無くても動きます)
作成手順
- 動画を一定時間ごとに切り出した画像群を生成します
- 画像群を結合して,新しい動画(タイムラプス動画)を作る
実行
- サンプル動画は,Pixabayの動画を使用しました(Coverr-Free-Footageさん感謝)
- 人-コマース-ショップ-忙しい-6387の720p(60fps)動画をPeople6387.mp4としてダウンロードしました
動画からコマ落とし画像を作成
- 入力動画から,コマ落としした画像群を生成します
mkdir out docker run --rm \ -v ${PWD}:/tmp/ \ ffmpeg-docker:latest \ -r 60 -stats -i /tmp/People6387.mp4 \ -r 15 /tmp/out/out_%04d.png
- コマンド説明
ffmpeg-docker:latest
: GPU(NVEnc)対応FFmpegのポータブルなDockerfileで作ったffmpegコンテナ-r 60
: 入力ファイルを60fpsとして扱う-r 15 /tmp/out/out_%04d.jpg
:
- 動画から画像を15フレームごとに切り出します
- 連番ファイル
out_0001.jpg
などの名前で,out
ディレクトリ以下に出力します- 実行が完了すると,以下のように多数のコマ落とし画像が生成されます.
コマ落とし画像を結合してタイムラプス動画を生成
- コマ落とし画像を結合して,タイムラプス動画を生成します
- 前節からの続きで,outディレクトリに連番ファイルが作成されているものとします
docker run --rm \ -v ${PWD}:/tmp/ \ ffmpeg-docker:latest \ -i /tmp/out/out_%04d.png \ -vcodec libx264 -pix_fmt yuv420p \ -r 60 /tmp/output.mp4
- コマンド説明
-i /tmp/out/out_%04d.png
: 入力ファイルを60fpsとして扱う-r 15 /tmp/out/out_%04d.jpg
: コマ落とし画像のファイル名パターンを指定-vcodec libx264 -pix_fmt yuv420p
: H.264動画を生成-r 60 /tmp/output.mp4
: output.mp4のファイル名で60fpsの動画を作成- 実行が完了すると,実行ディレクトリに
output.mp4
が作られています.
- このoutput.mp4が,元のPeople6387.mp4の動画を4コマ落ちした動画になっています.
- 上記コマ落ち動画は,FFmpegで動画をGIFに変換を参考に,作成したmp4ファイルをアニメーションgifに変換したものです.
参考
- 投稿日:2019-04-17T15:14:35+09:00
docker-hubを利用して簡易的なウェブアプリデプロイ環境を構築する
概要
bitbucketとdocker-hubを用いて自動ビルド&デプロイ環境を構築した
docker用インスタンスの作成
手元の環境ではlxcでサーバを管理していたため下記コマンドでlxcを立ち上げ&ip固定を行った。
lxc launch ubuntu:18.04 docker lxc network attach lxdbr0 docker eth0 eth0 lxc config device set docker eth0 ipv4.address <IPアドレス> lxc stop docker lxc config set docker security.nesting true #コンテナ内コンテナのセキュリティ許可 lxc start dockerまたlxcデフォルトテンプレートだとsshでkey認証が必須なので下記コマンドでgithubに保存してあるpublic keyを設定した。
curl https://github.com/{githubのアカウントid}.keys >> ~/.ssh/authorized_keysdocker用インスタンスの準備
下記コマンドでdocker-composeごとインストールします。
sudo apt update sudo apt install docker-compose sudo gpasswd -a $USER docker #aptで入れると実行する現在のアカウントが権限不足で実行エラーやroot権限が必要となってしまうためインストール後にグループ設定を行う。 sudo systemctl restart docker exit #一旦セッションを消すbitbucketリポジトリの作成
docker-hubで自動ビルドさせるリポジトリを作成していきます。今回はlaravelをデプロイする想定です。まず適当なディレクトリを作り下記の様なDockerfileとvhost.confを作成します。
FROM centos RUN yum install -y yum-utils epel-release RUN yum-config-manager --enable rhui-REGION-rhel-server-extras rhui-REGION-rhel-server-optional RUN rpm -Uvh http://rpms.remirepo.net/enterprise/remi-release-7.rpm RUN yum install -y --enablerepo=remi --enablerepo=remi-php72 php-cli php-zts php-intl php-mbstring php-dom php-pdo php-mysql php-pgsql php-devel php-gd php-zip RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \ && php -r "if (hash_file('sha384', 'composer-setup.php') === '48e3236262b34d30969dca3c37281b3b4bbe3221bda826ac6a9a62d6444cdb0dcd0615698a5cbe587c3f0fe57a54d8f5') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" \ && php composer-setup.php \ && php -r "unlink('composer-setup.php');" \ && mv composer.phar /usr/local/bin/composer RUN yum install -y python2-certbot-apache git httpd unzip ADD ./vhost.conf /etc/httpd/conf.d/vhost.conf ADD ./laravel /var/www/html RUN chmod -R 777 /var/www/html EXPOSE 80 EXPOSE 443 CMD httpd -DFOREGROUNDDocumentRoot /var/www/html/public ServerName localhost <Directory "/var/www/html"> AllowOverride All </Directory>次にdockerfileを作成したディレクトリでlaravelプロジェクトを立ち上げます。下記の様なコマンドで立ち上げます。
cd {dockerfileを作成してディレクトリ} sudo apt install composer php-zip composer global require "laravel/installer" PATH=$PATH:~/.config/composer/vendor/bin export PATH #パスは必要に応じて永続化(.bashrcに記述)等してください laravel new laravel rm laravel/.gitignore #開発もdocker前提なのでignoreを消してしまいます。開発用サーバーを分離したい等あれば別途設定してくださいこれをdockerfileごとbitbucketにpushします。(bitbucketのリポジトリ作成方法は割愛します)
git init git add . git commit -m "first commit" git remote add origin {bitbucketリポジトリ} git push -u origin HEADdocker-hubの自動ビルド設定
次にdocker-hubでリポジトリを作成します。アカウント設定で先ほどbitbucketにpushしたアカウントをconnectしておきます。そうするとリポジトリ作成時に下記の様にリポジトリが出てくるので先ほどpushしたリポジトリを選択しcreate&buildをクリックします。
docker-composeでの運用
docker-hubのbuild画面にてsuccessが出ていたらbuild完了です。build完了を確認したらdockerを動かしたいインスタンスにて適当なディレクトリを作成し下記の様なdocker-compose.ymlとvhosts.confファイルを作成します。一様dbもdockerで用意していますが今回は設定方法や管理方法等は一旦割愛します。
version: '3' services: db: image: mysql ports: - "3306:3306" environment: MYSQL_ROOT_PASSWORD: '0000' proxy: image: nginx ports: - "80:80" - "443:443" links: - laravel laravel: image: {先ほど作成したdocker-hubのリポジトリ} links: - dbserver{ listen 80; server_name {FQDN}; location / { proxy_set_header Host $proxy_host; proxy_pass http://laravel; } }ファイルの作成が完了したらdocker-compose.ymlがあるディレクトリで下記コマンドでコンテナを追加。
docker-compose up -d
サイトの開発
docker-composeを用いて本番環境とほぼ同一の環境の開発環境を構築する。先ほどのdocker-compose.ymlとvhosts.confをベースにローカル開発環境用にディレクトリを作成し下記ファイルを作成する。
version: '3' services: db: image: mysql ports: - "3306:3306" environment: MYSQL_ROOT_PASSWORD: '0000' proxy: image: nginx ports: - "80:80" - "443:443" volumes: - ./vhosts.conf:/etc/nginx/conf.d/default.conf links: - laravel laravel: build: ./laravel links: - dbserver{ listen 80; server_name laravel.localhost; location / { proxy_set_header Host $proxy_host; proxy_pass http://laravel; } }次にlaravelリポジトリをサブモジュールとして追加し新規リポジトリを作成しpush。
git init git submodule add {先ほど作成したlaravelのリポジトリ} laravel git add . git commit -m "first" git add remote origin {bitbucketの新規リポジトリ} git push origin HEAD開発環境の起動を行う基本的には本番インスタンスと同じで下記コマンドを実行し
laravel.localhost
にアクセスdocker-compose up -d
バージョンの更新
基本的にdocker-hubで設定したブランチに変更が入ると自動でビルドが開始されるのでブランチに変更を行いdocker-hub上でビルドが完了したら下記コマンドを本番インスタンスでdocker-compose.ymlがある場所で実行するだけで本番環境へのデプロイが完了する。
docker-compose pull docker-compose up -d
まとめ
以上が個人的に利用しているごく簡単なデプロイ環境の一例です間違っている箇所やもっと効率的にCI/CD等と連携できる方法等あればご教授いただけると助かります。
- 投稿日:2019-04-17T12:21:26+09:00
docker & docker-compose コマンド集
日時: 2019.4/17
Dockerバージョン: Docker version 18.09.2目的:よく使うdockerコマンドをメインに、基本的なdockerコマンドを集めていく
■よく使うシリーズ
docker-compose build
docker-compose.ymlの内容に基づいてイメージを作成する
$ docker-compose builddocker-compose up
docker-compose.ymlに書かれた依存関係をもとに、よしなにイメージを元にコンテナを作成と起動してくれる
イメージはdocker-compose buildで作成する$ docker-compose updocker-compose down
docker-compose.ymlに書かれている内容をみてコンテナを停止してくれる。また、そのコンテナとネットワークの削除もしてくれます
「--rmi all」オプションをつけると、コンテナだけでなく、もとになったイメージも削除してくれます。← 僕はこれはあんまり使わないです$ docker-compose downdocker ps
現在起動しているコンテナの一覧を見れる
$ docker psdocker ps -a
起動しているものと起動していないもの、全てのコンテナの一覧をみれる
$ docker ps -adocker images
Dockerfileを元にbuild(作成)したイメージの一覧を表示してくれる
$ docker imagesdocker logs (コンテナのID)
docker psコマンドで表示されるコンテナIDを引数に指定すると、logの確認ができます
$ docker logs■dockerコンテナの中に入って作業をしたい時
いくつか方法があるのでお好きな方法で作業してください
docker exec -it コンテナ名 bash
すでに動いているコンテナはdocker psコマンドで確認できます。コンテナ名も同時に確認できます
コンテナから抜けたい場合は、[Ctl] + [C]コマンドで抜けれます。$ docker exec -it nginx(← 例です) bashdocker attach コンテナ名またはコンテナID
注意点としては、
このコマンドでコンテナ内に入った場合、[Ctl] + [C]コマンドでコンテナから抜けるとコンテナは停止してしまいます。コンテナを起動したままコンテナから抜けたい場合は、
[Ctl] + [p], [Ctl] + [q] の両方を同時に押してください。$ docker exec -it nginx(← 例です) bash■その他dockerを使う上で必要なコマンド
dockerコンテナを削除
コンテナ名orコンテナIDは「docker ps -a」コマンドを実行すると見れます
$ docker rm コンテナ名orコンテナID
dockerイメージを削除
イメージIDは、「docker images」コマンドで確認できます
$ docker rmi イメージIDdockerイメージの詳細情報確認
イメージIDは、「docker images」コマンドで確認できます
$ docker inspect イメージIDイメージからコンテナの作成
コンテナの作成をするだけで、コンテナの起動はしません
$ docker create イメージIDその他にも、
「docker create」
「docker run」
「docker start」
「docker stop」
「docker restart」
「docker stats」
「docker pause」
「docker unpause」
「docker top」
「docker port」
「docker rename」
「docker cp」
「docker diff」
「docker version」
「docker info」
などがありますが、ここで書くよりもググったほうがわかりやすい記事があると思います笑
- 投稿日:2019-04-17T12:09:25+09:00
Dockerコンテナ上で起動したアプリケーションで時間がおかしい
はじめに
どうも@chan_kakuです
今回は社内ツールをDocker化した際に問題が発生したので、その問題と今回打った対策、そして苦労した点など紹介していきます。問題
今回はJavaのアプリケーションで問題が発生しました。
その問題というのは、とある箇所で、DBにて永続化させる時間をnew Date()
で現在時間をとって永続化させるような処理でした。
今までは、Dockerを使っておらず、ホストサーバのタイムゾーンに依存してましたので、JSTで時間が取れており、何も問題がありませんでした。
しかしながら、このツールをDocker化した後にこのnew Date()
での現在時間がずれるというような問題が発生してしまいました。原因と推測
そもそも自分はDockerについての知識があまりなく、Java側のコードに問題があると推測していました。
しかしながら、上記にあるように、現在時刻をnew Date()
でとっていたため、その推測は外れてしまいました。
次の推測としては、ホストサーバのタイムゾーンでした。しかしながら、date
コマンドを打つと、JST
と表示されたため、こちらも外れました。
僕の考えられる推測が尽きてしまったため、Dockerに詳しい方々に聞いたところ、どうやらDocker内にも時間があるらしく、こちらの時間がずれているのではないかという情報を得ました。
そこで、以下のコマンドでDocker内に入り、同じようにDockerコンテナ内のタイムゾーンを調べましたdocker run -t -i (対象のdocker image) /bin/bash → docker起動して中に入る # dateここでは先ほどと同じように
JST
となっておりました。対策
もはや、ここで万策尽きたと嘆いていたところ、とある方から鶴の一声が、、、
「Docker内で/etc/timezone
の中身見てみて〜」
この方を信じ、Docker内に入り、/etc/timezone
をみてみたところ、ここがUTC
となっていました
もしやということでDockerfileで/etc/timezone
をAsia/Tokyo
になるように以下のように修正しましたENV TZ="Asia/Tokyo" RUN echo $TZ > /etc/timezone // timezone関連以外の記載は省略今回あえてechoで変えた理由としては、ツールの要件的に時間系のタイムゾーンはJSTであることが求められていたので、ここはDockerfileに記載すべきだと判断しました
終わりに
Dockerについての知識があまりなかったためにいろんなところで苦労はしましたが、ひとまず解決して良かったです!
もっとこうした方がスマートだよなどのマサカリをお待ちしております
- 投稿日:2019-04-17T12:00:46+09:00
centos 7のdocker devicemapperのディスク割当を増やす
何故増やすのか
centos7のdockerはデフォルトでdevice mapperで動いているが、こいつがコンテナにデフォルトで割り当てるディスクサイズが10GBのため
環境
# cat /etc/redhat-release CentOS Linux release 7.6.1810 (Core)方法
前提
まず大前提としてdocker daemonを停止した上で
/var/lib/docker
を削除するコンテナやイメージが全て消えるので必要な場合はバックアップなどを取ること
なお、データをマウントしていたりする場合は当然そのデータは消えない
設定
筆者はsystemctlでdockerを起動しているため
/lib/systemd/system/docker.service
に設定を記述したdockerdを直接叩いている場合でも結局同じオプションになるはず
ExecStart=/usr/bin/dockerd -H fd:// -s devicemapper --storage-opt dm.basesize=20Gあとはsystemctlのreloadとstartでok
大事なのは
-s devicemapper
と--storage-opt dm.basesize=20G
の2つである
--storage-opt dm.basesize=20G
こいつが本体
割り当てたいディスクのサイズにしてもらえれば良い ただしグローバル設定なのですべてのコンテナがこのサイズになる
-s devicemapper
ハマりポイント
storage-opt dm.basesizeだけを設定するとなぜかoverlayfsが反応してエラーを出して動かなくなるので明示的にdevicemapperを使うことを知らせる
挙動があからさまにバグである
- 投稿日:2019-04-17T11:35:18+09:00
ganache-cli(Docker版)を永続化してみた
ganache-cli(Docker版)を永続化したい!
Docker版のganache-cliを使っていてデータを永続化したい!と思い調べてみました。
調べてみると、ganache-cliをインストールしてオプション実行する方法や、dockerファイルを自分で作成して永続する方法などみつかりましたが、ganache-cliのdockerをそのまま利用する方法が見つかりませんでした...
なので、ganache-cliのdockerをそのまま使ってぱぱっと永続化させてみました。
実施環境
Windows10
やってみる
ganache-cli GitHubをみると、コンテナを作成する際にオプション付ければ効くようなので、とりあえずやってみました。
ganache-cliで利用するオプションは以下の3つです。
--mnemonic : ニーモニックの指定
--networkId : ネットワークIDの指定
--db : チェーンを保存するディレクトリパスの指定チェーンを保存するディレクトリパスの他にニーモニックとネットワークIDを設定しないと永続化しないらしいので、上記オプションを設定して動作させていきたいと思います。
また、ポートフォワーディングもさせたいのでdockerで -p オプションも追加します。
それでは、やってみます!
$ docker run -it --name gt -p 8545:8545 trufflesuite/ganache-cli -i 5777 -m "test dignity cupboard vault crazy jar sand write trap humor glimpse feel" --db /data Ganache CLI v6.4.1 (ganache-core: 2.5.3) (node:1) [DEP0005] DeprecationWarning: Buffer() is deprecated due to security and usability issues. Please use the Buffer.alloc(), Buffer.allocUnsafe(), or Buffer.from() methods instead. { [Error: ENOENT: no such file or directory, open 'C:/Program Files (x86)/PortableGit/data/!blockHashes!0x68414881405b5bf39093d2be0612a7de4a9a3e01059a982c2078dd9ea6d944e1'] errno: -2, code: 'ENOENT', syscall: 'open', path: 'C:/Program Files (x86)/PortableGit/data/!blockHashes!0x68414881405b5bf39093d2be0612a7de4a9a3e01059a982c2078dd9ea6d944e1' } eth_blockNumberエラー!
パスが変なことになってる!!!VSCode上でunixコマンドを使うために「C:\git-sdk-64\git-bash.exe」を利用しているのですが、その辺りが影響して変になっている?
と、思うので普通にコマンドプロンプトから実行しています。$ docker run -it --name gt -p 8545:8545 trufflesuite/ganache-cli -i 5777 -m "test dignity cupboard vault crazy jar sand write trap humor glimpse feel" --db /data Ganache CLI v6.4.1 (ganache-core: 2.5.3) (node:1) [DEP0005] DeprecationWarning: Buffer() is deprecated due to security and usability issues. Please use the Buffer.alloc(), Buffer.allocUnsafe(), or Buffer.from() methods instead. { [Error: ENOENT: no such file or directory, open '/data/!blockHashes!0x85fe02f623d60cf97eee9951d08c54ec4fe1956ae32162999bbbcdcf0a754e8d'] errno: -2, code: 'ENOENT', syscall: 'open', path: '/data/!blockHashes!0x85fe02f623d60cf97eee9951d08c54ec4fe1956ae32162999bbbcdcf0a754e8d' } eth_blockNumberエラー!
でも、パスは変ではなくなりましたね...コンテナ上に指定したパスにディレクトリがないのが原因だと思うので、ありそうなパスを設定して再実行!
$ docker run -it --name gt -p 8545:8545 trufflesuite/ganache-cli -i 5777 -m "test dignity cupboard vault crazy jar sand write trap humor glimpse feel" --db /root Ganache CLI v6.4.1 (ganache-core: 2.5.3) (node:1) [DEP0005] DeprecationWarning: Buffer() is deprecated due to security and usability issues. Please use the Buffer.alloc(), Buffer.allocUnsafe(), or Buffer.from() methods instead. Available Accounts ================== (0) 0x7ac6fac041d4522f8c362d78efec60396364c559 (~100 ETH) (1) 0xb7e478a1b92c8312ce298fd1f7500ed8bedd4da7 (~100 ETH) (2) 0x0060cd130503b92b7ca6e35793002bd149e14486 (~100 ETH) (3) 0x4fdd03ea775a490242cbe1382997f477d2ccc59e (~100 ETH) (4) 0x77911940f91157234e18e77166514dbaba7f459d (~100 ETH) (5) 0x951d94319cf3bc6b4bff5a33779c5a723b0b0c4e (~100 ETH) (6) 0x46bfe6f8492e30d8465cfb8e71ac7b9c320e20e3 (~100 ETH) (7) 0x5aa02a3eb327018dfa8ab746b97ce18f86ceb2f2 (~100 ETH) (8) 0x0ea09203d9cbba052a21a2c24b68494775be57b0 (~100 ETH) (9) 0x9e694b573a7c0f9a0112248cbe5823490e69ad8e (~100 ETH) Private Keys ================== (0) 0xa7cc8232ebd84cb2e7ec6548247958349245f2f7fa5ae8932ee685c3e4e337cc (1) 0x68612f49e208e4520576b8d7c4a3a0a92099eff5ff0293c8783303a13eba92de (2) 0xe9af3d439fe3675abbd9b1d2460b1fa2197e110bf4d7251aeb0c5e137c97e788 (3) 0x2afd91ee7448708b7be2f7c4b6973bf6ba02973bdc2b8b10aeec3dfa632add06 (4) 0x8150961114932b58b4060f0f819fd971f0f644b7b7aabef8d93177e90d623dc2 (5) 0x26eca9d40ba07290aa3601e68d010d20116942eed3b7f1dd7bc33b219d00e4b6 (6) 0xbfa75666ec62f476673084b4b4c91366394c030bf17445f72da9b90b48e757a2 (7) 0x6b13e0784d3357311c77160475fa40eeeb2ed61352db113334b6b9346e0eaffd (8) 0xaa7006153d8c8563f54757ce862386ecfd8f3ff881d4489ebe1b315bc2d4a3a1 (9) 0xb543d4afa9f6bf79b7ea1c9db5e8c7fcbd38bcbc742a05285241c2c5da1a1c0b HD Wallet ================== Mnemonic: test dignity cupboard vault crazy jar sand write trap humor glimpse feel Base HD Path: m/44'/60'/0'/0/{account_index} Gas Price ================== 20000000000 Gas Limit ================== 6721975 Listening on 0.0.0.0:8545 eth_blockNumberいけました!!!!
これで/rootディレクトリ配下にganache-cliのデータファイルが作成されているはずです。
docker止めてもデータが永続化していることを確認するため、Metamaskで適当に送金してアカウント1のETHを減らします。次に一度ganacheのコンテナを停止、再起動します。
$ docker stop gt gt $ docker start gt gtganache(Docker)は再起動のたびにデータが初期化されるので、再度Metamaskにウォレットをインポートしてアカウント1のETHが減ったままであればデータが永続化されたはずです。
念のために(キャッシュなどが残っていないように)Metamaskを一度削除してから、再度ローカルネットに接続しウォレットをインポートします。
ETHが減っていません!初期化されていません!!
一応送金できるか確認。ちゃんと送金できました。
上手く永続化できていそうです。まとめ
Docker版のganache-cliを永続化してみました。
db用のパス指定先が存在するディレクトリしかできないのが(自動で作ってくれない)少し厄介ですが、これで再起動するたびに初期化されることはなくなりました!
出力先ディレクトリ配下のファイルを複製などすれば、スナップショット的な使い方ができそうで便利そうです!
※一応、dockerの共有ディレクトリ設定(自動でディレクトリが作成されるので)のパスをdb用のパス設定で実行してみたのですが、無理でした。
- 投稿日:2019-04-17T08:51:39+09:00
Docker でキーバインドControl + pが2回押さないと反応しない件
概要
Dockerにattach(Dockerに入るイメージ)し、コンソールで使うキーバインドControl+pを押すと1度目は反応せず、2度目で入力されたと見なされる。
Docker導入時、設定変更せずに使っていると起きるのだが...
原因としてはdettach keyというDockerにattachした後にdetach(Dockerから出るイメージ)するキーのデフォルト設定でControl+p Control+qが割り当てられているからであった。環境
Docker version 18.09.2, build 6247962
対応策
Dockerの設定ファイルでdetachキーを変更すればよい。
私の場合、以下の場所に設定ファイルがあるので、このファイルに"detachKeys"という設定を追加する。~/.docker/config.json{ "detachKeys": "ctrl-\\" }ちなみに
ctrl-\\
としましたが、キーバインドに設定されていないキーであればなんでもよいです。
ctrl-^
でもよいので使いやすいように変更できます。参考↓
Docker コンテナの中で、Ctrl-p を二回押さないと使えないdocker で Ctrl-p 2回押し問題 (detach-keys の問題) を解決するには
補足
macOS High Sierra 10.14.3
- 投稿日:2019-04-17T02:12:13+09:00
Docker Desktop for Windows で Windows GUI コンテナを動かす
これは何?
Windows10 1803 の Docker Desktop for Windows の docker container で Windows GUI アプリが動いた話です.
結論
次の組み合わせ
Dockerfile
使った Dockerfile です. たぶん ubuntu:latest でも動くと思います.
32bit アプリを動かすつもりなら, コメントアウトしているENV WINEARCH=win32
を有効にし, win32 環境を構築した方が良いDISPLAY 環境変数が無いまま, winetricks を呼び出すと, GUI が立ち上がらないようで, dockerfile に初期設定を押し込めました.
wine_mono と wine_gecko を install する場合は, wget で/root/.cache
に msi ファイルを落としておいて, Xvfb などで DISPLAY を捨ててやるとうまくいくと思う. 未確認.DockerfileFROM ubuntu:16.04 # @see https://qiita.com/fkshom/items/53de3a9b9278cd524099 RUN sed -i.bak -e "s%http://[^ ]\+%http://ftp.jaist.ac.jp/pub/Linux/ubuntu/%g" /etc/apt/sources.list RUN set -eux; \ apt-get update \ && apt-get install -y --no-install-recommends \ software-properties-common \ apt-transport-https \ wget \ ca-certificates \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* RUN set -eux; \ dpkg --add-architecture i386 \ && wget -nc https://dl.winehq.org/wine-builds/Release.key \ && apt-key add Release.key \ && apt-add-repository 'https://dl.winehq.org/wine-builds/ubuntu/' \ && rm Release.key \ && apt-get update \ && apt-get install -y --no-install-recommends --allow-unauthenticated \ winehq-stable \ winetricks \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* # for 32bit #ENV WINEARCH=win32 RUN set -eux; \ winetricks win7コンテナの起動
docker image を
wine:latest
で作成したとします.
下記のバッチファイルから起動すると, VcXsrv のxhost +
と docker run の DISPLAY を設定しに行きます.
Windows10 の言語が日本語じゃなかったら, "vEthernet (既定のスイッチ)" を適宜書き換えてください.launch.cmdset ARGS=%* set DISPLAY=127.0.0.1:0.0 "%PROGRAMFILES%\VcXsrv\xhost" + for /f "tokens=3 usebackq" %%i in (`netsh interface ipv4 show address "vEthernet (既定のスイッチ)" ^| find "IP アドレス"`) do @set IP=%%i docker run -it --rm -e DISPLAY=%IP%:0.0 %ARGS% wineこのバッチを使用し, 次のようにコンテナを起動します.
launch.cmd -w /root/tmp -v "C:\hogehoge":/root/tmpDockerfile で ENTRYPOINT を wine にしていないので bash が走るのがイマイチなので適当に直してください。
参考にしたところ
apt-getの利用リポジトリを日本サーバーに変更する
Ubuntu 18.04 / 16.04 に最新の wine をインストールする
DockerからGUIアプリとWineアプリを使う方法
NETSHコマンドで取得したIPアドレスを変数に入れて判別するバッチファイルを作る方法