まーぽんって誰がつけたの?

iOS→Scala→インフラなおじさん技術メモ

Deisのpostgresをoff-clusterにする(Cloud SQLを利用)

やること

postgresをoff-clusterする。 で、そのpostgresをどこに立てるかというとGCEにたてるかGCPのmangaedなCloud SQL(RDSみたいなやつ)があるので、どっちにするかということで、今回はCloud SQLを使ってみた。

Cloud SQL PostgreSQL beta

Cloud SQLのポスグレはまだbeta版。なので、terraformにも来てない。けど、GCPは全体的に、公式docにGUIでconsoleでやるか、CLIでやるかっていうのが並列で揃ってて、とても親切。AWSみたいに、AWS Consoleで突然何かが色々できてしまうってことがない感じ。

f:id:masato47744:20170422013302p:plain

作成は、rakeタスクにまとめるとこんな感じ。簡単

namespace :gcp do
  task :create_postgresql do
    sh %Q(gcloud beta sql instances create deis-cloudsql-postgres \
          --database-version=POSTGRES_9_6 \
          --activation-policy=ALWAYS \
          --backup-start-time=16:00 \
          --gce-zone=asia-northeast1-a \
          --maintenance-window-day=SUN \
          --maintenance-window-hour=17 \
          --tier=db-n1-standard-1 \
          --storage-type=SSD \
          --storage-size=10GB \
          --storage-auto-increase \
          --region=asia-northeast1
          )
  end
end

deis君からCloud SQLに接続するには

Cloud SQLに接続するには、何通りか方法があるけど、基本的にcloud sql proxyというのをたてて、それ経由でアクセスするっていうのがいい方法のようだ。 関係ないけど、Kubernetsとかにも言えるけどこういうのうまいなって思う。

cloud sql proxyを起動したマシン上で、localhostに対してDB接続しにいくと、つながるようになってる。

で、GKEの場合どうするかっていうと、sidecarパターンというやり方で、同一Pod内にcloud sql proxyのdocker image(imageが用意されてるところも憎い!)を起動させてそれをmountしてlocalhostをproxyとして接続できるようになる。うまい。

ただ、Deis君の場合は既にpodは固定されていて、ちょっと個別にpodを書き換えたりするのは面倒・・・っていうかやり方分からない。forkして、自分で追加したりしないといけないのかもしれない。なので、cloud sql proxyのパターンは使えない。もしかしたら、clusterたてるときに各nodeに初期処理入れてcloud sql proxyのinstallとかできるのかな。。

Cloud SQLは外部からアクセスする際に、IPアドレスを指定しておけば、そこからのアクセスは受け付けるようになる。なので、今回は、GKEのnodeのIPアドレスを指定してあげたら接続できた。 AWSだとNAT Gatewayがいたりするんだけど、GCPの場合はそういうのなさそう?やるなら、自分でNAT instance立てろ的な感じかもしれない。

nodeのIPアドレスたちは、kubectlコマンド使ってとれるようになってる。便利。この情報は、kubectl Cheat Sheetに書いてあった。

kubectl get nodes -o jsonpath='{.items[*].status.addresses[?(@.type=="ExternalIP")].address}'

deis用のユーザーとdatabaseの作成

GCPは、cloud shellっていうコンソールからssh接続できる風なやつが用意されてて、それ経由で作成してもOK。(AWSにも欲しい・・)

f:id:masato47744:20170422013525p:plain

ただ、これもCLIが用意されてるのでそっちのがいい。ので、そうした。

  task :create_database do
    sh %Q(gcloud beta sql users set-password postgres no-host \
          --instance=deis-cloudsql-postgres --password=#{ENV["POSTGRES_PASSWORD"]}
          )
    sh %Q(gcloud beta sql users create deis no-host \
          --instance=deis-cloudsql-postgres --password=#{ENV["POSTGRES_DEIS_PASSWORD"]}
          )
    sh %Q(gcloud beta sql databases create deis \
          --instance=deis-cloudsql-postgres
          )
  end

off-clusterにしてhelm install

さっき作ったdbの情報をvalues.yamlに書く。

cloud sqlIPアドレスは、gcloudコマンドでとれる。結果はYAML形式でかえってくるので、rubyだといい感じに処理できる。それを--set database.postgres.hostで渡してあげればOK。passwordとかも、環境変数に入れておいてinstallをする。

namespace :helm do
  task :install do
    sh %Q(helm install deis/workflow --namespace deis -f values.yml \
          --set gcs.key_json="$(cat gcloud-service-key.json | base64)" \
          --set database.postgres.password=#{ENV["POSTGRES_DEIS_PASSWORD"]} \
          --set database.postgres.host=#{sql_instance_address}
        )
  end
  
  def sql_instance_address
    res = `gcloud sql instances describe deis-cloudsql-postgres`
    YAML.load(res)["ipAddresses"].first["ipAddress"]
  end
end

動いているようだ

$ kd get pods
NAME                                     READY     STATUS    RESTARTS   AGE
deis-builder-1364651073-83r7x            1/1       Running   0          43m
deis-controller-2711454920-g32nn         1/1       Running   0          43m
deis-logger-176328999-06nwq              1/1       Running   2          43m
deis-logger-fluentd-fbg4m                1/1       Running   0          43m
deis-logger-fluentd-nt0tq                1/1       Running   0          43m
deis-logger-fluentd-ttlwl                1/1       Running   0          43m
deis-logger-fluentd-zc6hd                1/1       Running   0          43m
deis-logger-redis-304849759-njc4w        1/1       Running   0          43m
deis-monitor-grafana-432627134-b12zt     1/1       Running   0          43m
deis-monitor-influxdb-2729788615-6pl28   1/1       Running   0          43m
deis-monitor-telegraf-5rhmv              1/1       Running   1          43m
deis-monitor-telegraf-ckfvc              1/1       Running   1          43m
deis-monitor-telegraf-pdhj8              1/1       Running   1          43m
deis-monitor-telegraf-q0d2h              1/1       Running   1          43m
deis-nsqd-3597503299-sxcss               1/1       Running   0          43m
deis-registry-169338602-4hzmk            1/1       Running   0          43m
deis-registry-proxy-7h5f2                1/1       Running   0          43m
deis-registry-proxy-k8vv4                1/1       Running   0          43m
deis-registry-proxy-np9ks                1/1       Running   0          43m
deis-registry-proxy-xcsx8                1/1       Running   0          43m
deis-router-2126433040-7js0d             1/1       Running   0          43m
deis-workflow-manager-2528409207-55vjg   1/1       Running   0          43m

うまくいってないと、STATUSがCrashになってたり、RESTARTSが増え続ける。 ちなみに、kdkubectl --namespace=deisのalias。Tipsが公式に書いてあた

追記:これで解決

mpon.hatenablog.com

deisでminioを置き換えるだけをやってみる

公式ページを参考に

Configuring Object Storage

本当にここを変えただけ。bucketはGCSであらかじめ作っておく。権限とかもなんもしてない。

diff --git a/values.yml b/values.yml
index c25cfb2..1d94aca 100644
--- a/values.yml
+++ b/values.yml
@@ -7,7 +7,7 @@ global:
   # - azure: Store persistent data in Azure's object storage
   # - gcs: Store persistent data in Google Cloud Storage
   # - minio: Store persistent data on in-cluster Minio server
-  storage: minio
+  storage: gcs

   # Set the location of Workflow's PostgreSQL database
   #
@@ -75,9 +75,9 @@ gcs:
   # key_json is expanded into a JSON file on the remote server. It must be
   # well-formatted JSON data.
   key_json: <base64-encoded JSON data>
-  registry_bucket: "your-registry-bucket-name"
-  database_bucket: "your-database-bucket-name"
-  builder_bucket: "your-builder-bucket-name"
+  registry_bucket: "my-registry"
+  database_bucket: "my-database"
+  builder_bucket: "my-builder"

deisをinstallする

適当にRakeタスク作って、コマンド実行。--setでsecrety keyを渡せるのいいね:100:

namespace :helm do
  task :install do
    sh %Q(helm install deis/workflow --namespace deis -f values.yml \
          --set gcs.key_json="$(cat gcloud-service-key.json | base64)"
        )
  end
end

状態

確かにminioはいなくなった。

$ helm status nobby-gibbon
LAST DEPLOYED: Tue Mar 28 20:41:00 2017
NAMESPACE: deis
STATUS: DEPLOYED

RESOURCES:
==> v1/ServiceAccount
NAME                   SECRETS  AGE
deis-nsqd              1        9m
deis-workflow-manager  1        9m
deis-router            1        9m
deis-registry          1        9m
deis-database          1        9m
deis-builder           1        9m
deis-logger-fluentd    1        9m
deis-controller        1        9m
deis-logger            1        9m
deis-monitor-telegraf  1        9m

==> v1/Service
NAME                    CLUSTER-IP    EXTERNAL-IP     PORT(S)                                                   AGE
deis-monitor-influxui   10.3.254.23   <none>          80/TCP                                                    9m
deis-logger-redis       10.3.249.48   <none>          6379/TCP                                                  9m
deis-registry           10.3.250.48   <none>          80/TCP                                                    9m
deis-database           10.3.242.10   <none>          5432/TCP                                                  9m
deis-monitor-influxapi  10.3.241.35   <none>          80/TCP                                                    9m
deis-workflow-manager   10.3.242.232  <none>          80/TCP                                                    9m
deis-builder            10.3.244.107  <none>          2222/TCP                                                  9m
deis-logger             10.3.253.37   <none>          80/TCP                                                    9m
deis-monitor-grafana    10.3.245.167  <none>          80/TCP                                                    9m
deis-router             10.3.252.162  35.190.228.247  80:32439/TCP,443:30793/TCP,2222:30898/TCP,9090:30627/TCP  9m
deis-nsqd               10.3.246.21   <none>          4151/TCP,4150/TCP                                         9m
deis-controller         10.3.241.43   <none>          80/TCP                                                    9m

==> extensions/v1beta1/Deployment
NAME                   DESIRED  CURRENT  UP-TO-DATE  AVAILABLE  AGE
deis-monitor-grafana   1        1        1           1          9m
deis-controller        1        1        1           1          9m
deis-logger            1        1        1           1          9m
deis-nsqd              1        1        1           1          9m
deis-database          1        1        1           1          9m
deis-builder           1        1        1           1          9m
deis-workflow-manager  1        1        1           1          9m
deis-router            1        1        1           1          9m
deis-registry          1        1        1           1          9m
deis-logger-redis      1        1        1           1          9m
deis-monitor-influxdb  1        1        1           1          9m

==> extensions/v1beta1/DaemonSet
NAME                   DESIRED  CURRENT  READY  NODE-SELECTOR  AGE
deis-monitor-telegraf  4        4        4      <none>         9m
deis-registry-proxy    4        4        4      <none>         9m
deis-logger-fluentd    4        4        4      <none>         9m

==> v1/Secret
NAME                   TYPE    DATA  AGE
deis-router-dhparam    Opaque  1     9m
objectstorage-keyfile  Opaque  4     9m
minio-user             Opaque  2     9m

==> v1/ConfigMap
NAME                  DATA  AGE
dockerbuilder-config  2     9m
slugbuilder-config    2     9m
slugrunner-config     1     9m

GCSのbucketの中になんか入ってる

databaseにだけ入ってた。これがなんか状態を保存してるやつなんだろうな。

f:id:masato47744:20170422013114p:plain

deisのアンインストール

なんとなく分かったのでアンインストール

$ helm list
NAME            REVISION    UPDATED                     STATUS      CHART               NAMESPACE
nobby-gibbon    1           Tue Mar 28 20:41:00 2017    DEPLOYED    workflow-v2.12.0    deis
$ helm delete nobby-gibbon

これだけだと、secret情報が消えなかったので次に再度helm installするときに、エラーになってしまう。こんな感じで残っちゃうようだ。

$ kubectl get secrets -n deis
NAME                       TYPE                                  DATA      AGE
builder-key-auth           Opaque                                1         18m
builder-ssh-private-keys   Opaque                                3         18m
database-creds             Opaque                                2         18m
default-token-vdgrd        kubernetes.io/service-account-token   3         19m
deis-workflow-manager      Opaque                                1         17m
deploy-hook-key            Opaque                                1         18m
django-secret-key          Opaque                                1         18m
logger-redis-creds         Opaque                                1         18m

なので、コマンドラインで一気に消す。今までのぼくなら1個ずつ手動入力してたけどlazy nightに参加したのでがんばって怠惰になるためにワンライナーでやる。

$ kubectl get secrets -n deis | awk '{print $1}' | tail -n +2 | xargs kubectl delete secret -n deis

追記

ただ、helm deleteだけだと、完全には消えなかったので、helm delete --purgeをつける必要があった。

Deisをproduction運用するための設定の仕方調査

背景

Deisはhelmコマンドでインストールすれば簡単に入るけど、そのままだとデータ保存先がephemeralなものになってしまうのでダメ。他にもproduction運用のためにした方が設定があるのでまとめる。

調べ方

変えなきゃいけなさそうなところ

  • kubernetesのバージョン
  • クラスタインスタンスの推奨スペック
  • object storage はminioからGCS
  • postgres は Cloud SQL for postgresql or postgresql on GCE
  • docker registryはGCR
  • firewallの設定。そのままだと0.0.0.0/0からつながってしまう。
  • deis registerできるのをadminだけにする。controllerの設定
  • Grafanaのsign upをdisableにする。
  • SSLの設定。そのままだとオレオレ認証のまま?
  • monitorのinfluxDBの設定。そのままだとmetricsが永続化されない。
  • DNS
  • routerをscaleさせた方がいいとある -> 今回はいらないかも?

変え方

kubernetsの色んなリソースをまとめたものをChartという。まぁざっくりいうとyumとかhomebrew的なもの。 実はChartには海図という意味があり、kubernetesがギリシャ語で船長みたいな意味で、helmが舵という意味なので、kubernetesがchartをもとにhelmをとると考えればなんとなく意味が分かる。

Chartはある決まったstructureをしてるんだけど、それのうちパラメータを設定できるのがvalues.ymlというもの。CLI経由で--setで一つ一つパラメータを渡すこともできるけどめんどいから基本やらない。ただ、--setでoverrideするとか追加するとかはやりそうな気がする。 で、DeisもChartで管理されてるので、当然values.yml経由でパラメータを変更する。

values.ymlの持ってきかたは、helm inspectサブコマンドで取得できる。valuesをつけなければ、values以外の基本的な情報を取得できたりする。ちなみに、当然だけど、deis/workflow以外のものの場合も同様で、deis/workflowの部分をstable/mysqlとかにすれば、情報がとれる。

helm inspect values deis/workflow | sed -n '1!p' > values.yml

values.ymlの内容はこれ

global:
  # Set the storage backend
  #
  # Valid values are:
  # - s3: Store persistent data in AWS S3 (configure in S3 section)
  # - azure: Store persistent data in Azure's object storage
  # - gcs: Store persistent data in Google Cloud Storage
  # - minio: Store persistent data on in-cluster Minio server
  storage: minio

  # Set the location of Workflow's PostgreSQL database
  #
  # Valid values are:
  # - on-cluster: Run PostgreSQL within the Kubernetes cluster (credentials are generated
  #   automatically; backups are sent to object storage
  #   configured above)
  # - off-cluster: Run PostgreSQL outside the Kubernetes cluster (configure in database section)
  database_location: "on-cluster"

  # Set the location of Workflow's logger-specific Redis instance
  #
  # Valid values are:
  # - on-cluster: Run Redis within the Kubernetes cluster
  # - off-cluster: Run Redis outside the Kubernetes cluster (configure in loggerRedis section)
  logger_redis_location: "on-cluster"

  # Set the location of Workflow's influxdb cluster
  #
  # Valid values are:
  # - on-cluster: Run Influxdb within the Kubernetes cluster
  # - off-cluster: Influxdb is running outside of the cluster and credentials and connection information will be provided.
  influxdb_location: "on-cluster"
  # Set the location of Workflow's grafana instance
  #
  # Valid values are:
  # - on-cluster: Run Grafana within the Kubernetes cluster
  # - off-cluster: Grafana is running outside of the cluster
  grafana_location: "on-cluster"

  # Set the location of Workflow's Registry
  #
  # Valid values are:
  # - on-cluster: Run registry within the Kubernetes cluster
  # - off-cluster: Use registry outside the Kubernetes cluster (example: dockerhub,quay.io,self-hosted)
  # - ecr: Use Amazon's ECR
  # - gcr: Use Google's GCR
  registry_location: "on-cluster"
  # The host port to which registry proxy binds to
  host_port: 5555
  # Prefix for the imagepull secret created when using private registry
  secret_prefix: "private-registry"


s3:
  # Your AWS access key. Leave it empty if you want to use IAM credentials.
  accesskey: ""
  # Your AWS secret key. Leave it empty if you want to use IAM credentials.
  secretkey: ""
  # Any S3 region
  region: "us-west-1"
  # Your buckets.
  registry_bucket: "your-registry-bucket-name"
  database_bucket: "your-database-bucket-name"
  builder_bucket: "your-builder-bucket-name"

azure:
  accountname: "YOUR ACCOUNT NAME"
  accountkey: "YOUR ACCOUNT KEY"
  registry_container: "your-registry-container-name"
  database_container: "your-database-container-name"
  builder_container: "your-builder-container-name"

gcs:
  # key_json is expanded into a JSON file on the remote server. It must be
  # well-formatted JSON data.
  key_json: <base64-encoded JSON data>
  registry_bucket: "your-registry-bucket-name"
  database_bucket: "your-database-bucket-name"
  builder_bucket: "your-builder-bucket-name"

swift:
  username: "Your OpenStack Swift Username"
  password: "Your OpenStack Swift Password"
  authurl: "Swift auth URL for obtaining an auth token"
  # Your OpenStack tenant name if you are using auth version 2 or 3.
  tenant: ""
  authversion: "Your OpenStack swift auth version"
  registry_container: "your-registry-container-name"
  database_container: "your-database-container-name"
  builder_container: "your-builder-container-name"

# Set the default (global) way of how Application (your own) images are
# pulled from within the Controller.
# This can be configured per Application as well in the Controller.
#
# This affects pull apps and git push (slugrunner images) apps
#
# Values values are:
# - Always
# - IfNotPresent
controller:
  app_pull_policy: "IfNotPresent"
  # Possible values are:
  # enabled - allows for open registration
  # disabled - turns off open registration
  # admin_only - allows for registration by an admin only.
  registration_mode: "admin_only"

database:
  # The username and password to be used by the on-cluster database.
  # If left empty they will be generated using randAlphaNum
  username: ""
  password: ""
  # Configure the following ONLY if using an off-cluster PostgreSQL database
  postgres:
    name: "database name"
    username: "database username"
    password: "database password"
    host: "database host"
    port: "database port"

redis:
  # Configure the following ONLY if using an off-cluster Redis instance for logger
  db: "0"
  host: "redis host"
  port: "redis port"
  password: "redis password" # "" == no password

fluentd:
  syslog:
    # Configure the following ONLY if using Fluentd to send log messages to both
    # the Logger component and external syslog endpoint
    # external syslog endpoint url
    host: ""
    # external syslog endpoint port
    port: ""

monitor:
  grafana:
    user: "admin"
    password: "admin"
    # Configure the following ONLY if you want persistence for on-cluster grafana
    # GCP PDs and EBS volumes are supported only
    persistence:
      enabled: false # Set to true to enable persistence
      size: 5Gi # PVC size

  influxdb:
    # Configure the following ONLY if using an off-cluster Influx database
    url: "my.influx.url"
    database: "kubernetes"
    user: "user"
    password: "password"
    # Configure the following ONLY if you want persistence for on-cluster influxdb
    # GCP PDs and EBS volumes are supported only
    persistence:
      enabled: false # Set to true to enable persistence
      size: 20Gi # PVC size

registry-token-refresher:
  # Time in minutes after which the token should be refreshed.
  # Leave it empty to use the default provider time.
  token_refresh_time: ""
  off_cluster_registry:
    hostname: ""
    organization: ""
    username: ""
    password: ""
  ecr:
    # Your AWS access key. Leave it empty if you want to use IAM credentials.
    accesskey: ""
    # Your AWS secret key. Leave it empty if you want to use IAM credentials.
    secretkey: ""
    # Any S3 region
    region: "us-west-2"
    registryid: ""
    hostname: ""
  gcr:
    key_json: <base64-encoded JSON data>
    hostname: ""

router:
  dhparam: ""
  # Any custom router annotations(https://github.com/deis/router#annotations)
  # which need to be applied can be specified as key-value pairs under "deployment_annotations"
  deployment_annotations:
    #<example-key>: <example-value>

  # Any custom annotations for k8s services like http://kubernetes.io/docs/user-guide/services/#ssl-support-on-aws
  # which need to be applied can be specified as key-value pairs under "service_annotations"
  service_annotations:
    #<example-key>: <example-value>

  # Enable to pin router pod hostPort when using vagrant
  host_port:
    enabled: false

  # Service type default to LoadBalancer
  # service_type: LoadBalancer

workflow-manager:
  versions_api_url: https://versions.deis.com
  doctor_api_url: https://doctor.deis.com

このvalues.ymlの内容をproductionのための設定に変えていって、helm installとやる予定。

Terraform0.9.0からremote configがdeprecatedになる

remote configがdeprecatedになる

terraform0.9.0からterraformコマンド実行するとdeprecated warningがでてきます。

💁 3行

  • remote configコマンドがなくなってinitコマンドが導入される
  • backendという概念が導入されterraform.tfstateファイルを保存する設定をtfファイル側に書くようになってlocalのtfstateは更新されなくなる
  • ざっくりmigrationする際は、tfstateは一応バックアップしといて、tfファイルにbackendの設定書いて terraform initすればOK

設定の書き方

https://www.terraform.io/docs/backends/types/index.html ここの左ペインにStandard Backendsに色々なタイプがあるのでそこを見る。

例: S3

terraform {
  backend "s3" {
    bucket = "mybucket"
    key    = "path/to/my/key"
    region = "us-east-1"
  }
}

例: local

terraform {
  backend "local" {
    path = "relative/path/to/terraform.tfstate"
  }
}

そもそもなんでbackendというものを導入しようとしたのか、メリット的なものについて詳しく :pencil2:

該当のPR#11286に詳しく書いてある

  • remote configをより一般的な考え方にするために、backendという概念を導入した
  • 今ってterraform applyってやるタイミングでインフラ担当一人って感じだけど本当はいろんな箇所から複数同時に実行されることだってあるはず、将来はそうなるはず、そんな時のために、stateをlockしてくれる機能も自動的についてくる
  • remote configはコマンドラインベースでオプションを渡す形で設定していたのでわかりにくい、それをファイルベースで明示的に書くことでより基本的な設定とした
  • initコマンドに統合してmoduleのdownloadとかも一気にやってくれるようになる
  • 今まではremote configを見に行くコマンドと見に行かないコマンドがあったけど全部共通してbackendを通してコマンドを実行するようになる
  • localにキャッシュされてたtfstateファイルにsecret情報が書かれてしまってたがそもそもlocalにキャッシュされてたtfstateファイルはもう更新されなくなるのでgitに含めなくてOK
  • なので.gitignoreに.terraform/を追加しよう:exclamation:
  • localのtfstateのserialがremoteより高いとダメみたいなことが起きなくなる
  • LineageというUUID的なものが導入されてこの値が変わると別のinfraとみなされエラーが出るようになる。具体的なユースケースは間違って別環境のものでapplyしようとしたときとかだろうか 😐

terraform0.9.1からplanレベルでsecurity_groupのcidr_blocksのvalidateが入るようになった

0.9.1 changlog :tada:

https://github.com/hashicorp/terraform/blob/master/CHANGELOG.md#091-march-17-2017

provider/aws: Validate CIDR Blocks in SG and SG rule resources (#12765)

planで未然に防げる

つい間違ってcidr_blocksの方にsecurity_groupのidを指定しまったことがあったけどそれが防げるようになる💯

mpon.hatenablog.com

terraform consoleっていうREPLみたいなやつがあるの知らなかった

v0.8から導入された模様。俗に言うREPL。今のtfstateをもとにsyntaxを試すことができる。planやapplyなどを発行することはできないので安全。単にこういう時はterraformではどう書くんだろう??みたいなのを試したいときに便利。

例1: moduleのoutputを呼び出して要素を取得する

> "${module.base_network.private_subnet_001_ids}"
[
  subnet-xxxxxxxxx,
  subnet-yyyyyyyyy
]
> "${module.base_network.private_subnet_001_ids[0]}"
subnet-xxxxxxxxx
> "${module.base_network.private_subnet_001_ids[1]}"
subnet-yyyyyyyyy

例2: 組み込み関数が何を返すか調べる

> cidrsubnet("172.24.0.0/16", 10, 1)
172.24.0.64/26
> cidrsubnet("172.24.0.0/16", 10, 0)
172.24.0.0/26
> cidrsubnet("172.24.0.0/16", 26, 0)
cidrsubnet: insufficient address space to extend prefix of 16 by 26 in:
> cidrsubnet("172.24.0.0/16", 10, 4)
172.24.1.0/26
> cidrsubnet("172.24.0.0/16", 10, 5)
172.24.1.64/26
> cidrsubnet("172.24.0.0/16", 10, 6)
172.24.1.128/26
> cidrsubnet("172.24.0.0/16", 10, 8)
172.24.2.0/26

自分で立てたdeisにアプリをdeployするまで

quickstartのConfigure DNSregister an admin user and deploy your first app.をやっただけ。

ロードバランサーの確認

$ kubectl --namespace=deis describe svc deis-router | grep LoadBalancer
Type:           LoadBalancer
LoadBalancer Ingress:   104.198.90.182

これがロードバランサーのIP。

でも、これがいつできたのがわからない :scream:

On Google Container Engine, Deis Workflow will automatically provision and attach a Google Cloud Loadbalancer to the router copmonent.

Deis Workflowは自動的にGCPロードバランサーを作ってアタッチするっていってるけど、一体これはいつ行われたんだ・・ :thinking: helm install したときか?該当のコードをdeisのorganization配下でgcpとかload balancerとかgclbとかいろいろ検索してみたけど、わからず・・

kubernetesのserviceでtype:LoadBalancerとしてるから

そもそも、k8sにserviceというcomponentがあって、serviceは色んなタイプがあるけれどLoadBalancerを選ぶと、k8sがよしなにバックエンドのcloud providerを見てロードバランサーを作ってくれるってことですね。GKEの場合は、それは、Google cloud load balancerを使って実現すると。 なので、多分k8sのコードにそういうことが書いてあるはず(まだ見てないけど)。

deis/workflowがやるのはpodを作成するってことだけだと思いこんでしまっていて、podはcontainerを包含した単位なのに、なぜ上位のGCPの操作の仕方まで知っているんだろう?というところがつまづいてしまった原因でした:scream: そもそもk8sは何ができるのかを分かったようで分かってなかったってことですね。

ロードバランサーのIPをもとにnip.ioというサービスを利用したときのhostを確認する

$ host 104.198.90.182.nip.io
104.198.90.182.nip.io has address 104.198.90.182

これで deis.<GCPロードバランサーIPアドレス>.nip.io で手元でアクセスできるようになる。

$ curl http://deis.104.198.90.182.nip.io/v2/ && echo
{"detail":"Authentication credentials were not provided."}

手元のdeisコマンドでユーザー登録する

$ deis register http://deis.104.198.90.182.nip.io
username: admin
password:
password (confirm):
email: xx@bb
Registered admin
Logged in as admin
Configuration file written to /Users/mpon/.deis/client.json

既存のやつが上書きされちゃうっぽい。ってことは複数のdeis workflowに対して何かやりたいときは、deis registerをやり直さないといけないのか??ちょっとめんどいかもしれない :rolling_eyes:

$ deis register --help
Registers a new user with a Deis controller.

Usage: deis auth:register <controller> [options]

Arguments:
  <controller>
    fully-qualified controller URI, e.g. 'http://deis.local3.deisapp.com/'

Options:
  --username=<username>
    provide a username for the new account.
  --password=<password>
    provide a password for the new account.
  --email=<email>
    provide an email address.
  --login=true
    logs into the new account after registering.
  --ssl-verify=true
    enables/disables SSL certificate verification for API requests

deis registerのhelpを見てみても複数に切り替える的なやつはなさげだ。まぁいっか :dancer:

アプリケーションの器をdeis上に作る

$ deis create --no-remote
Creating Application... done, created jangly-sailfish
If you want to add a git remote for this app later, use `deis git:remote -a jangly-sailfish`

作ったとこにサンプルアプリケーションをpullする

$ deis pull deis/example-go -a jangly-sailfish
Creating build... done

アクセスする

$ curl http://jangly-sailfish.104.198.90.182.nip.io
Powered by Deis

コンフィグを上書きする

この間、勝手にローリングデプロイが行われるのでdown timeは0 💯

$ deis config:set POWERED_BY="Docker Images + Kubernetes" -a jangly-sailfish
Creating config... done

=== jangly-sailfish Config
POWERED_BY      Docker Images + Kubernetes
$ curl http://jangly-sailfish.104.198.90.182.nip.io
Powered by Docker Images + Kubernetes

スケーリング

コマンドでさくっとできる。

$ deis scale cmd=2 -a jangly-sailfish
Scaling processes... but first, coffee!
done in 10s
=== jangly-sailfish Processes
--- cmd:
jangly-sailfish-cmd-2108971595-1vxqg up (v3)
jangly-sailfish-cmd-2108971595-b7xhf up (v3)

Google Cloud Platformの環境をterraformで作る

そもそもGoogle Cloud Platformとは

Googleが出してるAWS的なやつ。GCPと略す。だいたいの比較表。

dev.classmethod.jp

なんでAWS使わないの?

コンテナでぽんぽんアプリケーションをdeployしたいんだけどそれをAWSでやろうとすると、ECSというサービスを使うことになる。でも、このECSというのがdeployまわりの取り扱いがめんどくさくてちょっといけてない。 で、同じようなコンテナのスケジューリングとかやってくれるソフトウェアでkubernetesというのがあるんだけど、それをAWS上にたてようとするとちょいめんどい。GCPにはGKE(Google Container Engine)というkubernetesのマネージドサービスが最初から用意されてるのでこれを使いたいから。

GCPにおけるprojectという概念

GCPは一つのアカウントに対して、projectという概念を持ってる。たくさんprojectは作ることができる。すごくざっくりだけど、AWSでいうVPCぐらいの単位かも。

GCPにおける地域とゾーン

GCPにもAWSでいうregion的なやつがある。

  • AWSでいうregionはGCPだと地域で、アジアだと日本(asia-northeast1)と台湾(asia-east1)がある
  • AWSでいうazはGCPだとゾーンと言って、日本には1a〜1cまである。

GCPのprovider

ここからterraformの話。とにもかくにもproviderを定義する。 GOOGLE CLOUD PROVIDER | terraform

provider "google" {
  credentials = "${file("../account.json")}"
  project = "elated-bebop-161001"
  region = "asia-northeast1"
}
  • account.jsonというのは、GCPのコンソールから認証情報(秘密鍵が書いてある)ファイルをjson形式で落としてきたもの。
  • projectは上で言及してるもので全GCP上で一意になるもの。Project名ではなくIDが振られるのでそれを書く。
  • regionは地域。ここでは日本を指定する。

terraformの実行🚀

terraformコマンドで直接実行してもいいけど、いろんなprojectを掛け持ちしてるとローカルのversionを揃えるのが大変なので、docker imagesで提供されているものを使う。 参考にしたサイト 実行するときにいろんなことをしたり、長いコマンドうつの面倒なのでRakefileを作ってそれで実行する。

Rakefileの中身とはまったポイント

  • 🙅 terraformのdocを見るとGOOGLE_CREDENTIALSに認証情報のjsonの中身をつめて渡せばそれを使ってくれるとかいてあったが、これをdockerのコマンドに渡すようにするとstdoutに表示されてしまう
  • 😓 出力されないように環境変数jsonの中身をいれようと試行錯誤したが、jsonの末尾の改行を取り除かないといけないのと、private keyが記述されてるのでそこに文字列として\nが入っててそれが環境変数として渡すと評価されてしまって改行扱いになってしまう

docker実行時に、GOOGLE_APPLICATION_CREDENTIALSで認証情報のファイルの場所を渡してあげるようにする💯

@terraform_version = "0.8.8"

@docker = %Q(docker run --rm -v ${PWD}:/work -w /work/terraform -e GOOGLE_APPLICATION_CREDENTIALS=/work/account.json hashicorp/terraform:#{@terraform_version})

namespace :tf do
  task :init do
    sh %Q(#{@docker} remote config \
            -backend=gcs \
            -backend-config="bucket=my-first-project-terraform-state-20170309" \
            -backend-config="path=terraform.tfstate" \
            -backend-config="project=elated-bebop-161001")
  end

  task :plan do
    sh %Q(#{@docker} plan)
  end
end

まとめ

俺はなぜあんな無駄な時間を・・ by 三井 寿

teeコマンドの使いどころ

標準入力を標準出力とファイルに出力する

というコマンドです。

なんか使いどころが分からないなーって思ってたけど、コマンドの結果をファイルに書き出してそれを別のコマンドに渡すってのをcircle ci上でやってて、あー、画面上でもこのファイルの中身見たいなーって思って、あっ!こういうときにteeを使いたくなるのか!!!ってなった。

terraformでif文的なものを使ったユースケース例

usecase

EC2インスタンス、ELB、security_groupあたりがまとまったmoduleがあるんだけど、特定の役割のインスタンスにだけiam_roleにSNSFullAccessをつけたい。

設定内容

  • allow_sns_full_accessっていう変数宣言しとく。
variable "allow_sns_full_access" {
  default = false
}
  • 対象のリソースでtrueならcount = 1、falseならcount = 0にする。
resource "aws_iam_role_policy_attachment" "app_sns_full_access" {
  count = "${var.allow_sns_full_access ? 1 : 0}"
  role = "${aws_iam_role.app.name}"
  policy_arn = "arn:aws:iam::aws:policy/AmazonSNSFullAccess"
}
  • 利用したいやつだけ、trueにする
module "app" {
  source = "../modules/app"

  allow_sns_full_access = true
}

結果

+ module.app.aws_iam_role_policy_attachment.app_sns_full_access
    policy_arn: "arn:aws:iam::aws:policy/AmazonSNSFullAccess"
    role:       "app"

AWSのロードバランサーあたりのセキュリティーグループ制限がさくっと緩和できなかった

http://docs.aws.amazon.com/ja_jp/general/latest/gr/aws_service_limits.html#limits_elastic_load_balancer

ロードバランサーあたりのセキュリティグループ 5

ご担当者様

平素よりお世話になっております。
AWSカスタマーサービスの藁品でございます。

折角ご申請いただきましたのに誠に申し訳ございませんが、「Classic Load Balancer」の
「ロードバランサーあたりのセキュリティグループの上限」を緩和することは致しかねます。
理由といたしましては、「ロードバランサーあたりのセキュリティグループ」は
「ネットワークインターフェースあたりのセキュリティグループ」に基づいている為でございます。

なお、下記サービスの上限値の変更を行うことで「ロードバランサーあたりのセキュリティグループの上限」の
緩和をすることも可能でございますが、この変更を行うことによりネットワークインターフェースを使用している
全リソースに影響を及ぼしますのでご留意くださいませ。

1) セキュリティグループあたりのインバウンド/アウトバウンドルール(デフォルト50)を減らす。
2) ネットワークインターフェースあたりのセキュリティグループを(デフォルト5)増やす。

引き続き緩和をご希望ということであれば、ケースにご返信いただくかたちでお知らせいただければ幸いです。
その他ご不明点、ご要望等ございましたら、ご遠慮なくお問い合わせくださいませ。

何卒よろしくお願いいたします。

terraformでsecurity groupをmodule化するときに気をつけること

IPアドレスを定義した共通のsecurity groupを作りたい

あるプロジェクトで外部のIPアドレスなどをまとめたものをsecurity groupとしてmodule化している。このアイディア自体はいいんだけど、環境ごとにこれを利用しようとした場合に失敗する。

例えば、こんな感じでmodulesに外部IPを書いたようなsecurity_groupをつくる。

terraform
├── development
│   └── main.tf
├── staging
│   └── main.tf
├── production
│   └── main.tf
└── modules
    └── external_ips_sg
        ├── security_group.tf
        └── outputs.tf

で、これらを各環境のmain.tfで利用すると、同一のsgを作ろうとするので、security groupの名前を変える必要がある。

module "external_ips_sg" {
  source = "../modules/external_ips_sg"
}

ここで、security groupのnameを変数化しておかないと同じ名前のsecurity groupを作ろうとして失敗する。

解決策

当たり前の話だけど、変数でnameを変えられるようにしておく。

└── modules
    └── external_ips_sg
        ├── variables.tf
        ├── security_group.tf
        └── outputs.tf
variable "env" {}
variable "vpc_id" {}
module "external_ips_sg" {
  source = "../modules/external_ips_sg"

  env = "dev"
  vpc_id = "vpc-xxxxxxxx"
}
resource "aws_security_group" "a" {
  name = "external-ips-${var.env}"
  vpc_id = "${var.vpc_id}"

ちなみに、Security GroupのDescriptionはForce new Resourceなのであとから変更できないので、何も設定しない方がいいかも。ちょっと文言変えたいなーってときにもう変更できない。何も設定しなければ、 Mangaed By Terraformのデフォルト文言が設定される。何か名前付けをしたい場合は、TagのNameを使おう。 あと、vpc_idを入れるのを忘れがち。vpc_idはなければ自動的にdefautl vpcが設定されるのでうまくいってしまう。

awsのsecurity groupにはルールの上限がある

IPアドレスをたくさん追加しようとしたら:scream:

各拠点のIPアドレスを追加してほしいという依頼で、terraform planで問題なかったので、applyしたらエラーが出た。

* aws_security_group.elb_app: Error authorizing security group ingress rules: 
RulesPerSecurityGroupLimitExceeded: 
The maximum number of rules per security group has been reached.
    status code: 400, request id: 88277351-1fc1-4e48-a005-3fe046a8145b

制限があるのを知らなかった。ドキュメントによると制限は50だった。

http://docs.aws.amazon.com/ja_jp/AmazonVPC/latest/UserGuide/VPC_Appendix_Limits.html#vpc-limits-security-groups

多分手動で追加してれば51個目でエラーになるから気づけるけど、terraformだとapplyしてはじめてエラーになる。なので、エラーになった瞬間、該当のsecurity groupの設定は空っぽになってしまう。

気をつけよう :cry:

とくにsecruty groupの変更は要注意

ingressとか書くときに、cidr_blocksと、security_groupsを指定できるけど、つい間違ってcidr_blocksの方にsecurity_groupのidを指定しまったことがあった。これも、planのときは問題ないけど、applyで指定方法が間違ってるってことでエラーになる。

ingress {
...
  cidr_blocks = [
     "${aws_security_group.hoge.id}"
  ]
}

まとめ

validateしたい。terraformにPRチャンスかもしれない。