進捗はありません

備忘録や研究メモ、日々の雑談や意見も

Istio on GKEでハマったこと

備忘録として書き留めます。コメントありましたらよろしくお願いいたします。

TL;DR

  • Terraformで、default nodeを削除してクラスタを立てると0 nodeになる
  • promsd (defaultのprometheus) はいくつかexporterがなく、かつReconcile設定が入っているのでConfigmapを書き換えられない
  • kialiやjaegerを入れる場合は、IstioのHelm Chartからの差分を手動でManifestに設定してapplyする必要がある

Istio on GKEとは?

cloud.google.com

Istio on GKEは、Google Kubernetes EngineでIstio有効にするためのアドオンです。これを利用することによって、わざわざクラスタにIstioをinstallしなくても、Istioが有効なクラスタが立ち上がります

通常Istioを入れる場合はHelm Chartを利用してInstallする必要があります

istio.io

ただ、Helmで入れない場合、いくつかハマりどころがあったのでそれを紹介したいと思います

default nodeを利用しないとPodが立ち上がらない

基本、GKEの構成管理はTerraformを利用することが多いのだが、ここで一つトラップがあった。

現在、柔軟なリソース管理のために、いくつかリソースの異なるNodeを利用してPodのスケジューリングを行うことができる。

cloud.google.com

その際、デフォルトのnodepoolを使わずにクラスタを立てる方法があり、terraformだと以下のように記述ができる

しかし、この方法でClusterを作成すると、後から作ったNodeが0Nodeとなって、Podがスケジューリングできない現象に遭遇した。エラーメッセージは、 Increase maximum size limit for autoscaling in one or more node pools that have autoscaling enabled. と表示される

この問題は、Default node poolを使ってクラスタを立てることで解決した。後からNode poolを足していくことは問題なさそう

promsdの設定が書き換えられない

Istio on GKEでIstioを入れた場合、デフォルトでpromsd(混乱を割けるためにpromsdという名前になったらしい https://cloud.google.com/istio/docs/istio-on-gke/release-notes

ただし、このpromsdはトラップがあって、Helmで入るPrometheusと設定が異なっているうえに、

labels:
    addonmanager.kubernetes.io/mode: Reconcile

が設定されたconfigmapで、後からPrometheusの設定を変更したり、exporterを追加したりすることができなかった

qiita.com

解決法としては、Istio on GKEの公式にも書かれている方法での独自PrometheusのInstallで落ち着いた

cloud.google.com

監視系の追加Install

KialiやJaegerといった監視や可視化のComponentsはIstio on GKEでは入っていない。 これらを入れるには、IstioのHelm Chartから上記Adaptersの差分を取り出してきて、手動でmanifestの作成を行う必要がある

cloud.google.com

どんな感じで設定するかというと、例えば、Kialiを入れたい場合、

curl -L https://git.io/getLatestIstio | ISTIO_VERSION=${CHART_ISTIO_VERSION} sh -

helm template --set kiali.enabled=false --namespace istio-system istio-${CHART_ISTIO_VERSION}/install/kubernetes/helm/istio > off.yaml
helm template --set kiali.enabled=true --namespace istio-system istio-${CHART_ISTIO_VERSION}/install/kubernetes/helm/istio > on.yaml
diff -u off.yaml on.yaml > kiali.yaml

といった感じで、Installする場合としない場合のyamlのdiffを取って、それらを手動で整形していくという形になる。これは運用事故りそうだなと感じてはいるが、現状はこの方法が一番ベストなはず

Grafana+PrometheusでKubernetesのリソース監視(Helmを利用した環境設定について)

TL;DR

  • PrometheusもGrafanaも,helmを利用することで簡単にInstallできる

github.com

  • GrafanaのDashboardはTerraformによる管理や,DashboardJson化ができるので,監視体制やDashboardを Infrastructure as Code として管理できる

www.terraform.io

Helmを利用したInstallについて

PrometheusもGrafanaも,k8sの監視ツールとしてはかなり有名なので,名前を聞いたことのある人も多いと思います 今回は,備忘録も兼ねてHelmを利用したk8sへの導入をまとめたいと思います.

helmを利用したInstallについては,こちらを参考にしました

qiita.com

なお,本記事は,クラスター及びクライアントでのHelmのセットアップが完了しているものとします

PrometheusのInstall

まずは,helmのvaluesの確認です.

github.com

こちらにHelmで設定ができる項目が入っていますが,実際のYamlでの記法を確認するのであれば,

helm inspect values stable/prometheus > prometheus_values.yaml を実行して,デフォルトのYamlを確認してみてください.

自分が設定しているvalues.yamlは以下のとおりです

## Define serviceAccount names for components. Defaults to component's fully qualified name.
##
serviceAccounts:
  alertmanager:
    create: false

server:

  persistentVolume:

    ## Prometheus server data Persistent Volume size
    ##
    size: 10Gi

  ## Use a StatefulSet if replicaCount needs to be greater than 1 (see below)
  ##
  replicaCount: 3

  statefulSet:
    ## If true, use a statefulset instead of a deployment for pod management.
    ## This allows to scale replicas to more than 1 pod
    ##
    enabled: true

alertmanagerをInstallしないのは,Grafana経由でのalertを行うため,必要がないからです また,replicaとstatefulsetを設定することで,autohealing時にも取得していた値を継続させます.

実行scriptは,以下のように行っています

#!/bin/bash

cd $(dirname $0)

echo Tune shell options && {
    set -o errexit
    set -o nounset
    set -o xtrace
}

: Define Variables && {
    readonly PROJECT="${1:?}"
    readonly CLUSTER="${2:?}"
    readonly REGION="${3:?}"
    readonly NAMESPACE="${4:?}"

    readonly PROMETHEUS_CHART_VERSION=8.8.0
}

: Set target project/cluser and tiller && {
    gcloud config set project "${PROJECT}"
    gcloud container clusters get-credentials ${CLUSTER} --region "${REGION}"
    kubectl config set-context $(kubectl config current-context) --namespace="${NAMESPACE}"
    helm init --client-only
}

: Deploy Prometheus using Helm && {
    helm upgrade --install \
        --namespace="${NAMESPACE}" \
        --version="${PROMETHEUS_CHART_VERSION}" \
        -f="values.yaml" \
        "prometheus" \
        stable/prometheus
}

解説をしていくと, Set target project/cluster and tillerに関しては,gcloudの設定と,clusterのcredentialsを取得し,helmの初期化を行っています.helmの初期化は,clusterにhelmを設定済みと仮定して,--client-onlyでの初期化を行っています

Deploy Prometheus using Helmでは,設定したvaluesに従って,prometheusをInstallしています.

ちなみに,Prometheusを外に出していないのは,Grafanaのみを外から見れるようにIngressを設定するためです.Prometheus自体を試したい場合は,kubectl port-forward svc/prometheus-server 3000:80 とport forwardingして,localhost:3000にアクセスすることでクエリの実行が可能になります

GrafanaのSetupについて

GrafanaのHelmの設定項目は github.com こちらにありますが,Prometheusと同様に,helm inspect values stable/grafana > grafana_values.yamlYamlの記法を確認できます

自分が設定するものは以下のとおりです

rbac:
  namespace: true

replicas: 3

## Enable persistence using Persistent Volume Claims
## ref: http://kubernetes.io/docs/user-guide/persistent-volumes/
##
persistence:
  enabled: true

## Pod annotations
podAnnotations:
    # 秘密情報が入れ替わったときにpodを入れ替えるためにhash値をアノテーションしておく
    checksum/extra-secret: ${SECRET_CHECKSUM:?}

## Expose the grafana service to be accessed from outside the cluster (LoadBalancer service).
## or access it from within the cluster (ClusterIP service). Set the service type and the port to serve it.
## ref: http://kubernetes.io/docs/user-guide/services/
##
service:
  type: NodePort

ingress:
  enabled: true
  annotations:
    kubernetes.io/ingress.global-static-ip-name: "static-ip"
    kubernetes.io/ingress.allow-http: "false"
    ingress.gcp.kubernetes.io/pre-shared-cert: "cert-name"
  path: /*
  hosts:
    - ${DOMAIN_URL}

# Use an existing secret for the admin user.
admin:
  existingSecret: "grafana-admin"

grafana.ini:
  server:
    domain: ${DOMAIN_URL}
    root_url: https://${DOMAIN_URL}
  auth.github:
    enabled: true
    client_id: ${AUTH_GITHUB_CLIENT_ID:?}
    client_secret: ${AUTH_GITHUB_CLIENT_SECRET:?}
    scopes: user:email,read:org
    auth_url: https://github.com/login/oauth/authorize
    token_url: https://github.com/login/oauth/access_token
    api_url: https://api.github.com/user
    allow_sign_up: true
    allowed_organizations: organization_name
  auth.google:
    enabled: ture
    client_id: "${AUTH_GOOGLE_CLIENT_ID:?}"
    client_secret: "${AUTH_GOOGLE_CLIENT_SECRET:?}"
    scopes: https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email
    auth_url: https://accounts.google.com/o/oauth2/auth
    token_url: https://accounts.google.com/o/oauth2/token
    allowd_domains: domain.example.com
    allow_sign_up: true

Grafanaでは,ログイン認証に様々なauthを利用できますが,上記の設定ではGithubGoogle認証を導入しています. 実際のkeyに関しては環境変数経由等で入れることを想定しています.導入すると,このようなログイン画面になります

f:id:kyo-bad:20190428110331p:plain

Ingressの設定もHelmで行うことができます.certやstatic-ipは任意の環境でお願いします

grafanaのAdminユーザとパスワードはSecret経由で取得することが推奨されています

apiVersion: v1
kind: Secret
metadata:
  name: grafana-admin
  namespace: ${NAMESPACE:?}
type: Opaque
data:
  admin-user: ${ADMIN_USER:?}
  admin-password: ${ADMIN_PASSWORD:?}

また,

## Pod annotations
podAnnotations:
    # 秘密情報が入れ替わったときにpodを入れ替えるためにhash値をアノテーションしておく
    checksum/extra-secret: ${SECRET_CHECKSUM:?}

これを設定することで,例えばKeyやIDを入れ替えたときにPodを自動的に入れ替えるようにしています.Grafana.iniが更新されても,自動で再読込をするわけではないので,grafana.iniに設定するものも含めてhash値にしてannotationsに設定することで,設定を自動で更新できるようにしています

Installのbashスクリプトは以下のとおりです

#!/bin/bash

cd $(dirname $0)

echo Tune shell options && {
    set -o errexit
    set -o nounset
    set -o xtrace
}

: Define Variables && {
    readonly PROJECT="${1:?}"
    readonly CLUSTER="${2:?}"
    readonly REGION="${3:?}"
    readonly NAMESPACE="${4:?}"

    # set secret env
    # readonly ADMIN_USER="${5:?}"
    # readonly ADMIN_PASSWORD="${6:?}"
    # readonly AUTH_GITHUB_CLIENT_ID="${7:?}"
    # readonly AUTH_GITHUB_CLIENT_SECRET="${8:?}"
    # readonly AUTH_GOOGLE_CLIENT_ID="${9:?}"
    # readonly AUTH_GOOGLE_CLIENT_SECRET="${10:?}"
    readonly GRAFANA_CHART_VERSION=2.2.1
}

: Set target project/cluser and tiller && {
    gcloud config set project "${PROJECT}"
    gcloud container clusters get-credentials ${CLUSTER} --region "${REGION}"
    kubectl config set-context $(kubectl config current-context) --namespace="${NAMESPACE}"
    helm init --client-only
}

: Create Secrets && {
    sigil -p -f secrets.yaml \
        NAMESPACE="${NAMESPACE}" \
        ADMIN_USER="$(echo ${ADMIN_USER} | base64 -w0)" \
        ADMIN_PASSWORD="$(echo ${ADMIN_PASSWORD} | base64 -w0)" \
    | kubectl apply -f - --record
}

: Deploy Grafana using Helm && {
    sigil -p -f grafana_values.yaml \
        DOMAIN_URL="${DOMAIN_URL}" \
        AUTH_GITHUB_CLIENT_ID="${AUTH_GITHUB_CLIENT_ID}" \
        AUTH_GITHUB_CLIENT_SECRET="${AUTH_GITHUB_CLIENT_SECRET}" \
        AUTH_GOOGLE_CLIENT_ID="${AUTH_GOOGLE_CLIENT_ID}" \
        AUTH_GOOGLE_CLIENT_SECRET="${AUTH_GOOGLE_CLIENT_SECRET}" \
        SECRET_CHECKSUM=$(echo \
            ${ADMIN_USER} \
            ${ADMIN_PASSWORD} \
            ${AUTH_GITHUB_CLIENT_ID} \
            ${AUTH_GITHUB_CLIENT_SECRET} \
            ${AUTH_GOOGLE_CLIENT_ID} \
            ${AUTH_GOOGLE_CLIENT_SECRET} \
        | sha256sum) \
    | helm upgrade --install \
        --namespace="${NAMESPACE}" \
        --version="${GRAFANA_CHART_VERSION}" \
        --values=- \
        "grafana" \
        stable/grafana
}

sigilを使って,Yamlのkey等を環境変数を利用して書き換え,grafanaをInstallしています.

詳細なスクリプトは本記事TopのGithubリンクにまとめてあります.

また,promQLでの設定やalert, terraformを利用したIaCもまとめていこうと思います

Prometheusについての参考記事

prometheus.io

qiita.com

qiita.com

Grafanaについての参考記事

grafana.com

10分で理解するGrafana - Qiita

qiita.com

社会人1年目を振り返って

今日で社会人1年目が終わるのでかんたんに振り返ろうと思います。ただのメモ書き

仕事について

自分はデータ解析者としてとある会社に新卒入社しました。もともとインターンをしていたというのもあり、会社自体の雰囲気は掴んでいたものの、インターンのときと異なる部署へ配属されたので、0からのスタートとなりました。 しかし、自分のやりたかったデータ解析から案件作成、分析から開発まで一通り自分で回すようなスタイルで仕事ができたおかげで、この1年で基本的なデータ活用施策の流れであったり、要所要所でのポイントなどを考えながら仕事をすることはできるようになりました。 また、自分の興味がエンジニアリングに向き始めたときに、勝手に分析とエンジニアリングの2足のわらじを履いていても誰も何も言わず自由にやれた部署であったので自由に仕事をすることはできたと思います。

また、いくつかのメディア取材を頂いたり、外部講演やイベントへのブース出展とデモアプリ等の作成、社内表彰での登壇など、露出の機会ももらえて1年間での学びは大きかったように思います。

ただ、この1年目での心残りは、もう少ししっかりやりたいことを明確にしたうえで仕事の優先順位を決めなかったため、タスク量が増えすぎてコントロールできなくなったり、保守的な考えに走って自ら周りを巻き込むような働き方ができなかったことが挙げられます。2年目は自分がやるべきこととやれること、やりたいことをはっきりさせた上で、よりスキル的な部分での学びのある仕事の仕方をしていきたいと思います。

個人について

入社してからというもの、休日も平日も仕事のことで頭がいっぱいになっていてプライベートをあまり楽しめていなかったりしたなと感じています。On Offの切り替えはしっかりした上で生産性高く仕事に取り組みたいです。 また、個人としての勉学という意味では1年目はあまりできなかった気がします。あくまで業務に必要な知識やスキルを業務内外でひたすら断片的に習得するといった感じで、体系的に何かを身につけるといった部分は弱かった気がします。 特に、個人としてのアウトプット量が学生の頃に比べてかなり落ちてしまいました。社内でのアウトプットを優先してしまっていたためというのと、学んだことを体系立てる作業を怠っていた結果だと思います。 個人としての目標を決めて、定期的にアウトプットを心がけて行きたいです。まずは月一くらいでしっかり何かしらのアウトプットを出せる学びを心がけます

来年度について

分析業務よりは、SRE的な仕事や、プロダクトを作るという部分をもっとやりたいなと感じたため、来年度はSREエンジニアとしてJob Changeをしようと思っています。 特に自分は、データサイエンティストやデータプランナーと呼ばれる人がもっとデータ活用をしやすくなるような仕組みづくりや基盤づくりをしていきたいと考えています。 この分野はこの1年でのハードスキルがあまり活かせないかもしれないですが、分析もエンジニアリングもできるからこそ、上記が実現できて、そのためのスキルを習得し利益貢献するために頑張っていきたいと思います。

Kubernetes完全ガイド読了

TL;DR

  • Kubernetes初学者〜中級者には最適な本だと感じた
  • 基本的なk8sの説明から、helm,Prometheus,Spinnaker, Istioといった周辺サービスまで一通り網羅されており、この本から更に学習を進められる
  • 買ってよかった1冊。k8s学びたい人は買って損はない本だと思う

概要

  • 最近、業務でk8sを触ることが多くなってきて、今までなんとなくで触ってきたので改めて1から勉強し直そうと考え、この本に出会った

Kubernetes完全ガイド (impress top gear)

Kubernetes完全ガイド (impress top gear)

  • 自分のインフラ周りの知識で言うと、Dockerをちょっと触ったことある、くらいのものだった
  • もともとデータサイエンスをやるつもりで入社したが、データプロダクトのSREや基盤開発が楽しくなり、Jobチェンジ中といった感じの状況

この本のいいところ

  • k8sの概念の基本的なところはコードを書きながら理解できる
  • k8sデバッグやログ調査等のコマンドも丁寧に乗っているので、このあたりのコマンドは業務でも活かせる

この本には乗っていないこと

  • k8s周辺のOSSの細かい説明やチュートリアル
    • helmとかPrometheusとかそのあたりは、公式ドキュメントを読みながら進めたほうがいいかもしれない
    • ただ、この本でもサラッとは紹介されており、どんな問題を解決するためのOSSなのかみたいなことはつかめるはず
    • 2019/03/17現在、Istio on GKEがBetaになっており、この本のInstall方法ではなく、Istio-Enabledなclusterを利用して自分はIstioを試していた。実行自体は特に問題なかった

terraform google providerの2.0へのUpgradeでBigtableをふっ飛ばした話

TL;DR

  • bigtable_instancecontainer_node_poolの記法が大きく変わったから、同じResource名を利用して、variableで環境分けている場合は注意しよう
  • plan結果をしっかり見て、v2へ上げたときにresourceの再作成が行われないようにしよう

概要

terraform-google-providerのv2が正式にリリースされました www.terraform.io github.com

これにともなって、いくつかのResource定義の記法が変更されており、特に注意しなければならない変更があります。 それは、google_container_node_poolgoogle_bigtable_instanceです

google_container_node_poolについて

こちらで大きく変わったのは、name_prefixを利用できなくなったことです。

従来、以下のように記述していたものが

resource "google_container_node_pool" "example" {
  name_prefix               = "example-np-"
  zone                           = "us-central1-a"
  cluster                        = "${google_container_cluster.example.name}"
  node_count                = 1

  node_config {
    machine_type = "${var.machine_type}"
  }

  lifecycle {
    create_before_destroy = true
  }
}

prefixを利用する場合はrandom_idというリソースから名前を生成する形がGuideに記述されています

variable "machine_type" {}

resource "google_container_cluster" "example" {
  name               = "example-cluster"
  zone               = "us-central1-a"
  initial_node_count = 1

  remove_default_node_pool = true
}

resource "random_id" "np" {
  byte_length = 11
  prefix      = "example-np-"
  keepers = {
    machine_type = "${var.machine_type}"
  }
}

resource "google_container_node_pool" "example" {
  name               = "${random_id.np.dec}"
  zone               = "us-central1-a"
  cluster            = "${google_container_cluster.example.name}"
  node_count         = 1

  node_config {
    machine_type = "${var.machine_type}"
  }

  lifecycle {
    create_before_destroy = true
  }
}

これによって、従来の記法で定義されていたNode_poolと、v2の記法で書かれたNode_poolが同一のものではなくなり、再作成となります。 回避方法は、Variable等を利用して、すでに作成されているnode_poolの名前をhard codingしてnameを定義する必要があります

google_bigtable_instance

これが非常に厄介で、Guideを引用すると

resource "google_bigtable_instance" "instance" {
  name         = "tf-instance"
  cluster_id   = "tf-instance-cluster"
  zone         = "us-central1-b"
  num_nodes    = 3
  storage_type = "HDD"
}

のような記法が

resource "google_bigtable_instance" "instance" {
  name = "tf-instance"
  cluster {
    cluster_id   = "tf-instance-cluster"
    zone         = "us-central1-b"
    num_nodes    = 3
    storage_type = "HDD"
  }
}

このような変更となり、clusterのfield内に定義するようになりました。

これだけならまぁ、対応可能な範疇です。

しかし問題なのは、instance_typeを定義していた場合です。

例えば、v1のときに、以下のような記法で開発環境と本番環境を作成していたとします


# dev.tfvars
bigtable = {
  instance_type = "DEVELOPMENT"
  num_nodes = "0"
}
# prod.tfvars
bigtable = {
  instance_type = "PRODUCTION"
  num_nodes = "3"
}
### この2つを環境ごとに切り分けて読み込む
variable bigtable {
  type = "map"
  default = {
    instance_type = "OVERWRITE"
    num_nodes = "OVERWRITE"
  }
}

resource "google_bigtable_instance" "instance" {
  name         = "tf-instance"
  instance_type = "${var.bigtable["instance_type"]}"
  cluster_id   = "tf-instance-cluster"
  zone         = "us-central1-b"
  num_nodes    = "${var.bigtable["num_nodes"]}"
  storage_type = "HDD"
}

ところが、v2系ではDEVELOPMENTの場合、そもそもnum_nodesを定義してはいけません。以下、公式抜粋

num_nodes - (Optional) The number of nodes in your Cloud Bigtable cluster. Required, with a minimum of 3 for a PRODUCTION instance. Must be left unset for a DEVELOPMENT instance.

つまり、このように定義していた場合、devとprdでresourceを2つ定義しなければなりません。


# dev.tfvars
bigtable = {
  instance_type = "DEVELOPMENT"
}
# prod.tfvars
bigtable = {
  instance_type = "PRODUCTION"
  num_nodes = "3"
}
### この2つを環境ごとに切り分けて読み込む
### countを利用して、条件分岐させる
variable bigtable {
  type = "map"
  default = {
    instance_type = "OVERWRITE"
    num_nodes = "OVERWRITE"
  }
}

resource "google_bigtable_instance" "instance" {
  count = "${var.bigtable["instance_type"] == "PRODUCTION" ? 1 : 0}"  

  name          = "tf-instance"
  instance_type = "${var.bigtable["instance_type"]}"

  cluster {
    cluster_id   = "tf-instance-cluster"
    zone         = "us-central1-b"
    num_nodes    = "${var.bigtable["num_nodes"]}"
    storage_type = "HDD"
  }
}

# devはnum_nodesを定義しない
resource "google_bigtable_instance" "instance_dev" {
  count = "${var.bigtable["instance_type"] == "DEVELOPMENT" ? 1 : 0}"  
  
  name          = "tf-instance"
  instance_type = "${var.bigtable["instance_type"]}"

  cluster {
    cluster_id   = "tf-instance-cluster"
    zone         = "us-central1-b"
    storage_type = "HDD"
  }
}

同一resourceは定義できないので、devとprodで2つのresourceを定義します。 そうなると、resource名が変更として扱われ、同一のnamecluster_idだったとしても再作成となります。(これを見逃していてふっ飛ばしました。しっかりplan見ようねって話なのですが...) 上記の例の場合、prodはresource名を変更していないので、changed扱い、devは新規作成及び旧instanceの削除となります。

オレ的開発環境管理(Docker)

個人のPCの開発環境の管理ってどうしてますか?

Dockerを利用して複数人の開発環境を統一できたり、簡単に環境を再構築したりWebアプリのテストができたりと、Dockerの開発環境の恩恵を大いに受けています。
個人での開発や分析でも特にこの恩恵は強く、どのマシンでも、例えばPythonやRの分析環境を持ち歩いたり、jupyterのextensionも含めてimageにしてしまえば本当に分析環境をまるっと持ち出せるのが便利でずっとDocker使ってますが、しばしばこんな問題にあたりませんか?

MacWindowsのvolume周りのコマンドが違ってつらい

例えば、Docker for Macでは、

docker run -v ./:/home/user/ <image>

のような指定で簡単にカレントディレクトリとコンテナのデータ共有ができると思いますが、Docker for windowsだとどうでしょうか。ご存知の方も多いでしょうが、これでは動きません。Windows(cmd)でカレントディレクトリの共有をしようとすると、基本的にフルパスを指定します(C:hogehogeみたいな感じで)
これ、めちゃくちゃ不便ですよね。私は家だとWindowsがメインで、研究室だとMacなので、同じDockerfileやdocker-composeでも、volume周りだけどうしても環境の統一ができない状態でした。

現在の開発/分析環境の管理方法

簡単にいうと、Makefileを使ってMacでもWindowsでも動くコマンドを管理して、make経由でコマンドを実行するようにしています。

分析環境の管理例(python)

ディレクトリ構成

workspace/
      ├Dockerfile
      ├dev.mk

まずはDockerfile。個人の設定そのまま入れているので、 パッケージ群とjupyterのextension設定を反映している。 パッケージとして注意していることは、tensorflowが入っていることでこれはインストール順でnumpy等の依存関係が上書きされるため、tensorflowだけ最後にインストールするようにしている。 個人の開発環境なのでrootで使っているが、外部サーバーに置く場合などはuserやセキュリティ設定は変えてます。

FROM python:3.6

MAINTAINER kyo-bad

RUN apt-get update \
    && apt-get install -y screen vim \
    && pip3 install --upgrade pip \
    && pip3 install --upgrade jupyter pandas matplotlib seaborn scikit-learn jupyter_nbextensions_configurator jupyterthemes \
    && pip3 install --upgrade tensorflow \
    && jupyter nbextensions_configurator enable --user \
    && git clone https://github.com/lambdalisue/jupyter-vim-binding $(jupyter --data-dir)/nbextensions/vim_binding \
    && jt -t monokai -T -N -altmd -vim

WORKDIR /root/workspace

続いて、dev.mkというmakefileを作成する。中身はこんな感じ

.PHONY: build run docker/*

WORK_DIR := $(CURDIR)
LOG_DIR := $(CURDIR)

BUILDER_IMAGE := python:myenv
BUILDER_WORK_DIR := /root/workspace
BUILDER_CMD := docker run --rm -it -v $(WORK_DIR):$(BUILDER_WORK_DIR) -w $(BUILDER_WORK_DIR) -p 6006:6006 -p 8888:8888 $(BUILDER_IMAGE)

setup:
  docker build -t $(BUILDER_IMAGE) .

rebuild-image:
  docker rmi $(BUILDER_IMAGE)
  docker build -t $(BUILDER_IMAGE) .

attach:
  $(BUILDER_CMD) bash

jupyter:
  $(BUILDER_CMD) jupyter notebook --ip 0.0.0.0 --no-browser --port 8888 --allow-root

tensorboard:
  $(BUILDER_CMD) tensorboard --logdir=$(LOGDIR)

Makefileで管理することのメリット

WIndowsでもMacでも同じコマンドで動く

これが一番大きいです。カレントディレクトリを共有する場合のコマンドの違いを、makeのCURDIRで隠蔽することで参照方法を統一している。 注意点としては、WORK_DIRをユーザのホームディレクトリにしてはいけないこと。これは、volume共有によってbashのログとかドットファイル類がホストにも共有されてしまうため。

長ったらしいコマンドを打たなくていい。

例えば、イメージのビルドだったら、

make -f dev.mk setup

これでいいし、jupyterの起動だったら

make -f dev.mk jupyter

でホストにサーバが立ち上がる。

angularでも似たような管理をしているので参考までに

Dockerfile(dev)

FROM node:8-alpine

MAINTAINER kyo-bad

RUN apk update \
    && apk add --no-cache --update alpine-sdk build-base python yarn \
    && yarn cache clean \
    && yarn global add @angular/cli@1.6.6 \
    && ng set --global packageManager=yarn \
    && apk del alpine-sdk \
    && rm -rf /tmp/* *.tar.gz ~/.npm \
    && yarn cache clean \
    && adduser -D appuser

COPY --chown=appuser:appuser project /home/appuser/app
ENV HOME=/home/appuser
USER appuser
WORKDIR ${HOME}/app
CMD [ "yarn && ng serve --host 0.0.0.0" ]

Makefileは以下

.PHONY: build run new yarn serve docker/* 

BUILDER_IMAGE := angular-yarn-alpine:myenv
BUILDER_WORK_DIR := /home/appuser/app
BUILDER_APP_NAME := app
BUILDER_CMD := docker run --rm -it -v $(CURDIR)/$(BUILDER_APP_NAME)/$(BUILDER_APP):$(BUILDER_WORK_DIR) -w $(BUILDER_WORK_DIR) -p 4200:4200 $(BUILDER_IMAGE)

setup:
  docker build -t $(BUILDER_IMAGE) .

rebuild-image:
  docker rmi $(BUILDER_IMAGE)
  docker build -t $(BUILDER_IMAGE) .

new:
  docker run --rm -v $(CURDIR):$(BUILDER_WORK_DIR) -w $(BUILDER_WORK_DIR) $(BUILDER_IMAGE) ng new $(BUILDER_APP_NAME) --routing

yarn:
  $(BUILDER_CMD) yarn

serve:
  $(BUILDER_CMD) ng serve --host 0.0.0.0 --poll 2000

attach:
  $(BUILDER_CMD) sh

Container SIG Meet-up 2017 Fallに参加してきました

10/31に行われた、Container SIG Meet-upに参加してきました。

connpass.com

twitter.com

個人の開発環境やクラウドでコンテナを動かしたりして遊んでいて、コンテナ技術に興味を持ったのがきっかけでした。「俺の環境では動く」問題を解決するために使っているだけなので、超にわかではありましたが、面白い話が聞けて良かったです。

DockerとKubernetesとRancher当たりの話から、DC/OSの話まで聞けて、自分でももう少し勉強してみようと思いました。 アプリ開発側として、

Tori (@toricls) | Twitter

この方の話は参考になりました(話が上手いっていうのもあるが)。自分はAnsibleを使っているが、確かにべき等性保つコードが書けているかとか非常に気を使うが、そんなことしなくても、Vagrant+VirtualBoxでContainer Linux(CoreOS)のVMを一枚ラップして、コンテナで環境を作ることでWinでもMacでもどこでも同じ環境かつ継続的なアップデート対応などができるって話で、私も今からローカル開発環境を見直そうと思っている (研究室の環境も一度見直したほうがいいかもしれん...)

わかっている範囲での関連資料一覧

www.creationline.com

qiita.com

www.slideshare.net

www.slideshare.net

www.slideshare.net

speakerdeck.com

github.com

github.com