自作したiOSライブラリの更新手順
自作したiOSのライブラリをCocoaPodsとCarthageで公開する記事はたくさんありますが、新バージョンの公開に関する記事はほとんど無く毎回困るのでまとめます。
Carthage
Carthageで公開しているライブラリの新バージョンをリリースするのは簡単で、GitHubで新しいrelease(タグ打ち)をするだけです。
そのため、準備が整ったらmasterにマージしてタグを打てばOKです。
CocoaPods
CocoaPodsは少し作業が発生します。ただ、公開するときに実行したコマンドの一部をもう一度実行するだけなので、忘れなければ簡単な作業です。
podspecの更新
podspecファイルに書いてあるバージョンをリリース予定のバージョンに更新します。
そのほかの内容は基本的に更新する必要はありません。もちろん、ここが変わるような変更を加えたリリースなら更新する必要があります。
.swift-versinoの更新
最近のXcodeには複数のバージョンのSwiftが付属しているため、ライブラリが使用するSwiftのバージョンを教えてあげないと正しく動かない場合があります。そのため.swift-version
というファイルを作成してリポジトリのルートに配置します。新バージョンのリリースでSwiftのバージョンが変わる場合はこのファイルに書いてあるバージョンも更新します。
バリデーション
更新したpodspecファイルに問題ないかチェックします。
pod lib lint
かpod spec lint
を実行します。
pod spec lint
はリポジトリのチェックまで行うため、新しいバージョンのタグが存在しない場合はエラーになります。未公開の場合はpod lib lint
を実行します。こちらはローカルにある情報だけでチェックします。
新バージョンの公開
ドキュメントの更新なども済ませたらmasterにマージして新バージョンのタグを打ちます。
その後、pod trunk push
を実行してpodsの公開は完了です。
前回のリリースから時間がたっているとpodsの認証が切れてしまっているので、pod trunk register hoge@hoge.com 'Hoge Huga'
と実行して再度認証し、その後もう一度リリースのコマンドを実行します。
複数のPodのログをまとめて表示する
Kubernetesを開発環境として使用しプロダクションと同じような環境で開発すると、複数のアプリケーションが可動することになると思います。
そうするとログも複数の場所に出力されることになります。
複数個のターミナルを立ち上げ1つずつログを表示してもいいですが、場所を取りますし確認する箇所が増えて面倒なので、まとめて表示する方法を紹介します。
今回は以下の2つの構成に合わせたやり方を紹介します。
- ログを標準出力に書き出している場合
- ログをファイルに書き出している場合
ログを標準出力に書き出している場合
これは stern
というアプリケーションを使えば簡単にできます。
使い方は簡単でPodの名前を引数として渡すだけです。
正規表現でいい感じに検索してくれて、オプションを入れればコンテナで絞り込むこともできます。
Dockerの標準に沿ったアプリケーションならこれを使えば解決です。
ログをファイルに書き出している場合
様々な理由によりDockerの標準に合わせることができず、ファイルにログを書き出している場合もあると思います。
この場合は kubectl exec
で tail
することになりますが、複数のPodのログをまとめるためには少し工夫をしなければいけません。
名前付きパイプを使う
名前付きパイプを使って複数の kubectl exec
の出力を1つにまとめます。
名前付きパイプについての説明はこの記事がわかりやすいです。
名前付きパイプに kubectl exec
を出力し、名前付きパイプを cat
することでログをまとめて表示します。
シェルスクリプトで一連の処理を書きます。
#! /usr/bin/env bash FIFO="/tmp/shout" mkfifo ${FIFO} trap 'rm ${FIFO}; exit 1' 1 2 3 15 PODA=$(kubectl get po -o=jsonpath='{.items[?(@.metadata.labels.app=="pod-a")].metadata.name}') PODB=$(kubectl get po -o=jsonpath='{.items[?(@.metadata.labels.app=="pod-b")].metadata.name}') PODC=$(kubectl get po -o=jsonpath='{.items[?(@.metadata.labels.app=="pod-c")].metadata.name}') kubectl exec -i ${PODA} -c hoge -- tail -f /var/app/log/hoge_log > ${FIFO} | \ kubectl exec -i ${PODB} -c hoge -- tail -f /var/app/log/hoge_log > ${FIFO} | \ kubectl exec -i ${PODC} -c hoge -- tail -f /var/app/log/hoge_log > ${FIFO} | \ cat ${FIFO}
stern
と比べると見やすさや使いやすさで数段劣りますが、同じようにまとめて出力することができます。
GKEでプリエンプティブインスタンスを使いこなす
GCPにあるプリエンプティブ インスタンスをGKEでうまいこと使えないか、試行錯誤した結果をまとめます。
プリエンプティブ インスタンスとは
一言で言ってしまえば、AWSにあるスポットインスタンスのGCP版です。
公式ドキュメントにはこのように書いてあります。
プリエンプティブ VM は、最長持続時間が 24 時間で、可用性が保証されない Google Compute Engine VM インスタンスです。プリエンプティブ VM は標準的な Compute Engine VM よりも低価格で、同じマシンタイプとオプションを使用できます。
ドキュメントに書かれているように低価格で使用できるのが最大のメリットです。
だいたい1/3ぐらいの価格で使用することができます。
しかし、様々な制限があります。
意識しておかなければならないこととして、次のようなことがあります。
- いつ終了するかわからない
- 最大でも24時間でシャットダウンされる
- 常に使用できるとは限らない
これらの注意点と上手に付き合いながらGKEのNodeとして使用していきます。
使いこなす
シャットダウンの対策
まず、シャットダウンによりNode数が減ってしまう問題はGKEを使っている上では問題ありません。
KubernetesがNode数の減少を検知して、即座に元と同じ数になるようにNodeを起動し直してくれます。
しかし、一時的とはいえNodeが減るので、そこで可動しているPodは止まってしまします。
すぐに復帰するので、冗長化されているPodであればそこまで問題でもありません。せいぜい、一時的(数秒から数十秒)に別のPodに負荷が集中するぐらいです。
アプリケーションの構成などの理由により冗長化できないPodや一時的でも減っては困るPodに対しては、プリエンプティブ インスタンスに配置されないように設定をします。
プリエンプティブ インスタンスの回避
Kubernetesのtaintsとtolerationsという機能を使用してプリエンプティブ インスタンスを回避します。
まず、通常のインスタンスで構成されるノードプールとは別に、プリエンプティブ インスタンスだけで構成されたノードプールを作成します。 プリエンプティブのノードプールを作成するときに、ノードtaintを設定します。
そして、プリエンプティブ インスタンスに配置されても問題ないPodにtolerationを設定します。
spec: template: spec: containers: ... tolerations: - key: gke-preemptible operator: Equal value: "true" effect: NoSchedule
これでこの設定がされているPodのみがプリエンプティブ インスタンスのノードプールに配置されるようになります。
taintsとtolerations
ちょっと仕組みがわかりにくいので、taintsとtolerationsの概念を説明します。
Nodeにtaints(よごれ)をつけ、そのtaintsをtolerations(寛容、黙許)できないPodをNoSchedule(スケジュールしない)と設定しています。
そしてPodにtolerationするtaintの内容を記述することで、taintのついたNodeに配置されるようになります。
24時間でシャットダウンされる
上で書いたプリエンプティブ インスタンスの回避の設定をしていれば24時間でシャットダウンされる制限も問題ないように感じるかもしれません。
しかし、24時間たつ前にシャットダウンされることがなかった場合、ノードプール内の複数のインスタンスが同時に24時間を経過し、同じタイミングでインスタンスがシャットダウンする可能性があります。
これだと、いくら冗長化していても運しだいですべてのPodが消えてしまう可能性があり、リスクが高くなります。
この問題の解決方法は簡単で、ノードプールを追加後、起動時間がバラけるように一部のNodeをシャットダウンしてしまえば良いです。
そうすることで複数のNodeが同時に24時間を迎えてシャットダウンされることが防げます。
常に使用できるかわからない
プリエンプティブ インスタンスがシャットダウンされたあと、GCPの状況によってはプリエンプティブ インスタンスが起動できないことがあります。
この場合、通常のインスタンスで構成されるノードプールにクラスターのオートスケールを設定しておくことで、そっちにインスタンスが追加されPodが配置されます。
tolerationsの設定がされていれも問題なく通常のインスタンスに配置されます。
なぜならtolerationsはtaintsを受け入れる設定なので、taintsが無いNodeにも配置されます。
まとめ
- 通常のインスタンスとプリエンプティブ インスタンスでノードプールを分ける
- プリエンプティブ インスタンスのノードプールにはtaintsを設定する
- 複数のプリエンプティブ インスタンスが同時に24時間をむかえないように起動時間を調整する
これを意識することでGKEを低価格で運用することができます。
ぜひ試してみてください。
GKEからCloud SQLに接続する
GKEからCloud SQLに接続するやり方としてCloud SQL Proxyを使う方法が推奨されています。
公式ドキュメントで解説されているやり方は、Cloud SQLにアクセスしたいコンテナが入っているPodにサイドカーとしてProxyを入れる方法です。
公式の例だと、wordpressのコンテナとProxyのコンテナを同じPodにしています。
GKE内にCloud SQLに接続するコンテナが一種類であれば公式のやり方で問題ありませんが、複数のサービスがCloud SQLにアクセスするとなると、全てのコンテナにサイドカーとして入れることになってしまい、あまりうれしくありません。
そこでProxyを単体のPodとして作成し、Serivceを経由してアクセスする方法をとります。
Deploymentの作成
基本的には公式ドキュメントと同じように進め、サイドカーとなっている部分だけを残し、適切なポートを開放してあげれば完了です。
注意点として、他のPodからのアクセスを受け入れる場合は、起動スクリプトでIPを指定しなければなりません。
ポート番号だけだと、ローカルホストからのアクセスしか受け付けてくれません。
具体的にはこのようにします。
command: ["/cloud_sql_proxy", "-instances=sample-165109:asia-northeast1:sample=tcp:0.0.0.0:3306", "-credential_file=/secrets/cloudsql/credentials.json"]
あとは適切にServiceを作成すれば完成です。
デメリット
サイドカーではなくPod単体として起動させるということは、クラスタ内のDBにアクセスするPodが全てこのPodを経由することになります。
そのため、このPodが起動していないと、全てのPodはDBにアクセスできなくなってしまいますので、きちんと冗長化しておきましょう。
定義ファイル
最後に定義ファイルの全体を貼っておきます。
apiVersion: apps/v1 kind: Deployment metadata: name: sql-proxy labels: app: sql-proxy spec: replicas: 2 selector: matchLabels: app: sql-proxy template: metadata: labels: app: sql-proxy spec: containers: - name: cloudsql-proxy image: gcr.io/cloudsql-docker/gce-proxy:1.11 ports: - name: mysql containerPort: 3306 command: ["/cloud_sql_proxy", "-instances=sample-165109:asia-northeast1:sample=tcp:0.0.0.0:3306", "-credential_file=/secrets/cloudsql/credentials.json"] volumeMounts: - name: cloudsql-instance-credentials mountPath: /secrets/cloudsql readOnly: true volumes: - name: cloudsql-instance-credentials secret: secretName: cloudsql-instance-credentials --- apiVersion: v1 kind: Service metadata: name: sql-proxy spec: selector: app: sql-proxy ports: - name: mysql protocol: TCP port: 3306 targetPort: mysql
Istioで使うTLS証明書をcert-managerで作成する
Istioは0.7まではIstio IngressというIngress Controllerの一種を使用してトラフィックを受け入れていましたが、0.8以降はGatewayを使うようになりました。
Istio Ingressでは他のIngressと同様のやり方でTLS証明書を扱えたのでcert-managerと組み合わせるのも簡単でしたが、Gatewayと組み合わせる場合は少々複雑になってしまいました。
今回はGatewayでcert-managerが管理する証明書を使用する方法を紹介します。
また、cert-managerの使い方は既に紹介しているので、そちらを参照してください。
この記事の内容を把握していることを前提として進めます。
環境
- GKE 1.9.7-gke.1
- Istio 0.8
- cert-manager v0.3.1 (DNS-01)
- route53
GatewayでTLS証明書を使う方法
TLS証明書を使う方法は厳密に定められており、これから外れてしまうと動きません。
istio-system
というnamespaceに証明書を入れたsecretを作成する- 証明書を入れたsecretの名前は
istio-ingressgateway-certs
でなければならない - Istioが証明書が入ったsecretを読み込み、
/etc/istio/ingressgateway-certs
に展開する
という仕様になっています。
詳細は公式ドキュメントに書かれています。
この仕様をクリアできるようにcert-managerの設定をしていきます。
cert-managerの設定
Istioの仕様をクリアするために、cert-managerに対して証明書が入ったsecretをistio-systemに作るように設定します。
cert-managerはIssuerとCertificateのあるnamespaceにsecretを作成するため、IssuerとCertificateのnamespaceをistio-systemにします。
metadata: namespace: istio-system
あとはCertificateにある作成するsecretの名前を決めるフィールドで、istio-ingressgateway-certsという名前を指定するだけです。
spec: secretName: istio-ingressgateway-certs
これでIstioから読み込む準備ができました。
IstioのGatewayからはドキュメントにある通りに記述すればhttpsで通信ができるようになります。
spec: tls: mode: SIMPLE serverCertificate: /etc/istio/ingressgateway-certs/tls.crt privateKey: /etc/istio/ingressgateway-certs/tls.key
route53で管理するドメインの証明書をcert-managerで管理しGKEで使用する
GCPでKubernetesを使用する場合、AWSと違いTLS証明書を発行してくれるサービスが存在しません。
有料の証明書を購入すれば良い話ではありますが、機能は同じなので無料でいきたいところです。
そこで、今回はLet's Encryptを利用したいと思います。
しかし、Let's Encryptは90日で証明書の期限が切れてしまい管理が大変なので、Kubernetesのアドオンであるcert-managerを使用したいと思います。
環境
- GKE 1.9.7-gke.1
- cert-manager v0.3.1
- route53
Let's Encryptの認証方法
cert-managerではLet's EncryptのACMEプロトコルであるHTTP-01とDNS-01に対応しています。
ACMEプロトコルについての解説は他に譲りますが、HTTP-01だとhttpアクセスを受け入れるため、ingressとの関わりが生まれてしまいます。
できるだけシンプルに管理したいので、今回はDNS-01を使用します。
また、DNS-01だとDNSのTXT レコードを操作するため、DNSへのアクセス権を付与する必要があります。
DNSへアクセスできない場合はHTTP-01で行うしかありません。
HTTP-01のやり方はこの記事(英語)がわかりやすいです。
cert-managerのインストール
cert-managerはhelmでインストールします。
$ helm install --name cert-manager --namespace kube-system stable/cert-manager
その他のパラメーターなどの詳細はこちらで確認できます。
route53へアクセスするための設定
IAMの作成
DNS-01で認証するのでroute53に書き込みができるIAMを作成します。
公式ドキュメントにIAMのポリシーが書かれていますが、古くなっているのか執筆時(2018/06/28)では動きませんでした。
ですが、すでにこの事がissueで議論されており、動作するポリシーが書かれているのでそれを利用します。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "route53:GetChange", "Resource": "arn:aws:route53:::change/*" }, { "Effect": "Allow", "Action": "route53:ChangeResourceRecordSets", "Resource": "arn:aws:route53:::hostedzone/*" }, { "Effect": "Allow", "Action": "route53:ListHostedZonesByName", "Resource": "*" } ] }
このポリシーを適用したIAMユーザーの認証情報をダウンロードしておきます。
cert-managerが読み込むsecretの作成
cert-managerはAWSのAccess Keyはyamlに生で書き込み、Secret KeyはKubernetesのsecretを経由して読み込みます。
そのため、まずはSecret Keyをsecretとして定義します。
$ echo -n 'youreSecretKey' | base64
apiVersion: v1 kind: Secret metadata: name: prod-route53-credentials-secret type: Opaque data: secret-access-key: hogePiyoFaaaa==
base64でエンコードしたsecretKeyをsecret-access-keyに入れます。
Issuerの作成
Issuerという名前の通り、Let's Encryptに発行を依頼するのに必要な情報を記述します。
ドメインの情報は、次に作成するCertificateで定義するのでそれ以外の情報をここで定義します。
apiVersion: certmanager.k8s.io/v1alpha1 kind: Issuer metadata: name: letsencrypt-prod spec: acme: server: https://acme-v02.api.letsencrypt.org/directory email: hoge@hoge.com privateKeySecretRef: name: letsencrypt-prod dns01: providers: - name: prod-dns route53: accessKeyID: YOUREROUTE53ACCESSKEYID secretAccessKeySecretRef: name: prod-route53-credentials-secret key: secret-access-key
emailとaccessKeyIDを正しいものに書き換えます。
Certificateの作成
作成する証明書のドメイン情報を定義します。
apiVersion: certmanager.k8s.io/v1alpha1 kind: Certificate metadata: name: sample-com spec: secretName: hoge-tls issuerRef: name: letsencrypt-prod commonName: 'hoge.com' dnsNames: - hoge.com acme: config: - dns01: provider: prod-dns domains: - '*.hoge.com' - hoge.com
ドメインを正しいものに書き換えてください。
Certificateが存在するnamespaceにsecretNameで指定した名前でTLS証明書がsecretに加工された状態で作成されます。
証明書の作成
ここまでに作成したIssuerとCertificateをKubernetesに適用します。
そうするとcert-managerでの管理が始まり、証明書が作成されます。
作成状況はdescribeコマンドで確認できます。
$ kubectl describe certificate ... Type Reason Message ---- ------ ------- ... Normal CeritifcateIssued Certificated issued successfully
このような出力になれば証明書の作成に成功しています。
この証明書はcert-managerによって管理され、失効まで30日を切ると更新されます。
Kubernetesのdashboardをingressから表示する
Kubernetesの状態が知りたいなと思い調べたところ、公式のWebUIであるdashboardをみつけました。
README通りにやれば簡単にインストールできますが、proxy経由でしか表示させる方法が書いてなく不便だったので、ingressを使用して表示させる方法を書きます。
証明書が入ったsecretの作成
dashboardは証明書がないとアクセスできない設定になっているので、まずは証明書を作成します。
dashboardのアクセスにドメインを使用する場合はLet's Encryptで簡単に証明書が取得できるので、取得しましょう。
Let's Encryptの使い方は他のサイトに譲ります。
ドメインがない場合は自己証明書を用意します。
このやり方はdashboardのwikiに書いてあるので、それ通りにやればOKです。
Certificate management · kubernetes/dashboard Wiki · GitHub
証明書が準備できたらsecretの作成に入ります。
$ kubectl create secret tls kubernetes-dashboard-certs --key dashboard.key--cert dashboard.crt -n kube-system
dashboardはkube-systemというnamespaceに作成されるので、このnamespaceにsecretを作成する必要があります。
dashboardのインストール
dashboardのインストールは公式にある通りにapplyするだけです。
$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/master/src/deploy/recommended/kubernetes-dashboard.yaml
ingressの設定
基本的には通常と同じようにingressを設定すれば問題ありませんが、1つだけ注意しなければいけないことがあります。
ingress-nginxの場合は、namespaceごとにingressを作成する必要があります。
そのため、今回はkube-systemというnamespaceにingressを作成します。
apiVersion: extensions/v1beta1 kind: Ingress metadata: annotations: kubernetes.io/ingress.class: nginx nginx.ingress.kubernetes.io/secure-backends: "true" name: dashboard-ingress namespace: kube-system spec: rules: - host: "sample.dash.jp" http: paths: - path: / backend: serviceName: kubernetes-dashboard servicePort: 443 tls: - hosts: - "sample.dash.jp" secretName: kubernetes-dashboard-certs