自作した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 lintpod 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 というアプリケーションを使えば簡単にできます。

github.com

使い方は簡単でPodの名前を引数として渡すだけです。
正規表現でいい感じに検索してくれて、オプションを入れればコンテナで絞り込むこともできます。

Dockerの標準に沿ったアプリケーションならこれを使えば解決です。

ログをファイルに書き出している場合

様々な理由によりDockerの標準に合わせることができず、ファイルにログを書き出している場合もあると思います。

この場合は kubectl exectail することになりますが、複数のPodのログをまとめるためには少し工夫をしなければいけません。

名前付きパイプを使う

名前付きパイプを使って複数の kubectl exec の出力を1つにまとめます。

名前付きパイプについての説明はこの記事がわかりやすいです。

qiita.com

名前付きパイプに 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ぐらいの価格で使用することができます。

f:id:akaimo3:20180722164618p:plain

しかし、様々な制限があります。
意識しておかなければならないこととして、次のようなことがあります。

  • いつ終了するかわからない
  • 最大でも24時間でシャットダウンされる
  • 常に使用できるとは限らない

これらの注意点と上手に付き合いながらGKEのNodeとして使用していきます。

使いこなす

シャットダウンの対策

まず、シャットダウンによりNode数が減ってしまう問題はGKEを使っている上では問題ありません。
KubernetesがNode数の減少を検知して、即座に元と同じ数になるようにNodeを起動し直してくれます。

しかし、一時的とはいえNodeが減るので、そこで可動しているPodは止まってしまします。
すぐに復帰するので、冗長化されているPodであればそこまで問題でもありません。せいぜい、一時的(数秒から数十秒)に別のPodに負荷が集中するぐらいです。

アプリケーションの構成などの理由により冗長化できないPodや一時的でも減っては困るPodに対しては、プリエンプティブ インスタンスに配置されないように設定をします。

プリエンプティブ インスタンスの回避

Kubernetesのtaintsとtolerationsという機能を使用してプリエンプティブ インスタンスを回避します。

まず、通常のインスタンスで構成されるノードプールとは別に、プリエンプティブ インスタンスだけで構成されたノードプールを作成します。 プリエンプティブのノードプールを作成するときに、ノードtaintを設定します。

f:id:akaimo3:20180722171308p:plain

そして、プリエンプティブ インスタンス配置されても問題ない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にも配置されます。

まとめ

これを意識することで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の使い方は既に紹介しているので、そちらを参照してください。
この記事の内容を把握していることを前提として進めます。

akaimo.hatenablog.jp

環境

  • GKE 1.9.7-gke.1
  • Istio 0.8
  • cert-manager v0.3.1 (DNS-01)
  • route53

GatewayTLS証明書を使う方法

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を使用したいと思います。

github.com

環境

  • 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のやり方はこの記事(英語)がわかりやすいです。

github.com

cert-managerのインストール

cert-managerはhelmでインストールします。

$ helm install --name cert-manager --namespace kube-system stable/cert-manager

その他のパラメーターなどの詳細はこちらで確認できます。

github.com

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はAWSAccess 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の使い方は他のサイトに譲ります。

ドメインがない場合は自己証明書を用意します。
このやり方はdashboardwikiに書いてあるので、それ通りにやれば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

これで指定したドメインにアクセスするとdashboardのUIが表示されます。