MinikubeでKubernetesクラスタが動く環境を作る
Minikubeではローカル環境にKubernetesクラスタを作成することができます。
GKE等のサービスと変わらない感覚でkubectl
コマンドを叩けるので結構便利でした。
マルチノードに対応しているわけではないので、それなりに本番環境とはマニフェストの差異が出るとは思いますが、とりあえず動かしてみる目的であればローカルで動くというのは大きなメリットだと思います。
準備
- VirtualBoxのインストール
ここからインストールできる。
6.0.4である程度の動作は確認済み。 - minikubeのインストール
Macなら以下で入る。その他はこちら。
$ brew cask install minikube
minikubeの起動
$ minikube --vm-driver virtualbox start
vm-driverの指定が必須。面倒なのでaliasを指定してある。
alias 'minikube start'='minikube --vm-driver virtualbox start'
この時点ではリモートのDockerイメージを利用する設定になっているので、以下のコマンドを叩いておきます。
$ eval $(minikube docker-env)
これを行うことによって、ホストマシンの docker
コマンドでminikube VM内のDockerデーモンと通信できるようになり、ローカルでビルドしたイメージがそのまま使えるようになります。
以下のコマンドで戻せます。
$ eval $(minikube docker-env -u)
停止
$ minikube stop
kubernetes contextの切り替え
# 一覧 $ kubectl config view # minikubeに切り替え $ kubectl config use-context minikube
参考
GKE上にMySQL/Redisを乗せて永続化させる
GKE上にMySQLとかRedisをわざわざ乗っけるならCloud SQLとかMemoryStoreとか使っといた方が耐障害性たけーよなとか思うんですが料金がお手軽じゃないです。
なので試しにGKEにのせてみることにします。
replicaは1台です。
Secret / ConfigMap の登録
--- apiVersion: v1 kind: Secret metadata: name: mysql-secret data: MYSQL_ROOT_PASSWORD: <BASE64_ENCODED_ROOT_PASSWORD> MYSQL_DATABASE: <BASE64_ENCODED_DATABASE> MYSQL_USER: <BASE64_ENCODED_USER> MYSQL_PASSWORD: <BASE64_ENCODED_PASSWORD> --- apiVersion: v1 kind: ConfigMap metadata: name: mysql-config-file data: custom.cnf: | [mysqld] default_authentication_plugin=mysql_native_password character-set-server=utf8mb4 [client] default-character-set=utf8mb4
$ kubectl apply -f mysql-config.yml
まずMySQLの設定に必要なConfigMapとSecretをapplyしておきます。
Secretの値にはbase64エンコードされた値を入れておく必要があります。
(一応エンコードが必要ないstringDataなるフィールドもある)
$ echo -n "<TEXT>" | openssl enc -e -base64
これでエンコードできる。
Mysql Serviceの登録
--- apiVersion: v1 kind: Service metadata: name: mysql spec: ports: - port: 3306 selector: app: mysql clusterIP: None --- apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 kind: StatefulSet metadata: name: mysql spec: selector: matchLabels: app: mysql replicas: 1 serviceName: mysql template: metadata: labels: app: mysql spec: containers: - image: mysql:8.0.15 name: mysql ports: - containerPort: 3306 name: mysql envFrom: - configMapRef: name: mysql-config-file - secretRef: name: mysql-config volumeMounts: - name: mysql-persistent-storage mountPath: /var/lib/mysql subPath: mysql - name: mysql-config-volume mountPath: /etc/mysql/conf.d/custom.cnf subPath: custom.cnf volumes: - name: mysql-config-volume configMap: name: mysql-config-file volumeClaimTemplates: - metadata: name: mysql-persistent-storage spec: accessModes: [ "ReadWriteOnce" ] resources: requests: storage: 20Gi
$ kubectl apply -f mysql.yml
volumeClaimTemplatesにClaimの情報を書き込んだ上で、そのClaimをvolumeMountsで指定してやります。 これで自動的にPersistentVolumeが立つようになります。
GKEではPersistent Volumeのデフォルトとして GCEPersistentDisk が作成されます。 デフォルトではHDDなので、SSDで作成したい場合はStorageClassを追加してspec.storageClassNameを指定する必要があります。
MySQLのデプロイでちょっとハマったんですが、マウントするストレージのディレクトリにはいくつかファイル(ディレクトリ)があるようで、MySQLは初回起動時にデータを保管するディレクトリが空でない場合にエラーを吐くため、Podが立ちませんでした。
対策として、subPath: mysql
を指定することでPersistentVolumeに mysql/
ディレクトリを作成して、そのディレクトリを /var/lib/mysql
にマウントするようにしています。
Redis Serviceのデプロイ
apiVersion: v1 kind: Service metadata: name: redis spec: ports: - port: 6379 clusterIP: None selector: app: redis --- apiVersion: apps/v1 kind: StatefulSet metadata: name: redis spec: selector: matchLabels: app: redis serviceName: redis replicas: 1 template: metadata: labels: app: redis spec: containers: - name: master image: redis:alpine ports: - containerPort: 6379 volumeMounts: - name: redis-persistent-storage mountPath: /data subPath: redis volumeClaimTemplates: - metadata: name: redis-persistent-storage spec: accessModes: [ "ReadWriteOnce" ] resources: requests: storage: 1Gi
$ kubectl apply -f redis.yml
MySQLとあんまり変わらないです。 確認してみます。
# keyをセットする $ kubectl exec -it redis-0 redis-cli 127.0.0.1:6379> set key redis OK 127.0.0.1:6379> exit # podを落として再度作成されるまで待つ $ kubectl delete pod redis-0 pod "redis-0" deleted # 新しくできたpodに入ってkeyをgetしてみる $ kubectl exec -it redis-0 redis-cli 127.0.0.1:6379> get key "redis"
大丈夫そうです。
このままだとスケールしませんが節約にはなります。
でも節約考えるならGKE使わなくてもいいんだよなぁ…
Kubernetesにデプロイされているアプリケーションのデバッグ
雑にデバッグしたい時用。
Podのステータス確認
$ kubectl get pods
Podの詳細確認
$ kubectl describe pod <POD_NAME>
ログ確認
一番これを使う気がする。
$ kubectl logs <POD_NAME> # マルチコンテナPodの場合 $ kubectl logs <POD_NAME> -c <CONTAINER_NAME>
コンテナが稼動していて中に入りたい場合
$ kubectl exec -it <POD_NAME> <COMMAND> # e.g. /bin/bash
リソース確認
# nodeの確認 $ kubectl top node <NODE> # podの確認 $ kubectl top pod <POD>
Google Container Registry にイメージをアップロードする
公式がより詳しい。
準備
$ gcloud auth configure-docker
Dockerイメージにタグをつける
$ docker tag <SOURCE_IMAGE> <HOSTNAME>/<PROJECT_ID>/<IMAGE>:<TAG>
- HOSTNAME
gcr.io | us.gcr.io | eu.gcr.io | asia.gcr.io |
---|---|---|---|
米国(変更の可能性あり) | 米国(gcr.ioとは別のバケット) | 欧州 | アジア |
今回はアジア鯖に上げる。
ホストにpush
$ docker push asia.gcr.io/<PROJECT_ID>/<IMAGE>:<TAG>
確認
# Image確認 $ gcloud container images list --repository asia.gcr.io/<PROJECT_ID> # Tag確認 $ gcloud container images list-tags asia.gcr.io/<PROJECT_ID>/<IMAGE>
gcloud command cheatsheet
基本的なところだけ。 こういうの devhints とか建てて書いといたほうがいいんだろうなぁ…
auth
# ログイン $ gcloud auth login # リスト $ gcloud auth list
config
# リスト $ gcloud config list # アカウント変更 $ gcloud config set account <ACCOUNT>
project
# リスト $ gcloud projects list # 変更 $ gcloud config set project <PROJECT_ID>
compute
# リージョンリスト $ gcloud compute regions list # リージョン変更 $ gcloud config set compute/region <REGION> # asia-northeast1 # リージョンリスト $ gcloud compute zones list # リージョン変更 $ gcloud config set compute/zone <REGION> # asia-northeast1
components
$ gcloud components update # アップデート $ gcloud components install <COMPONENT> # インストール。kubectlとか。 $ gcloud components list # リスト
address
# グローバルIP作成 $ gcloud compute addresses create <IP_NAME> --global # 確認 $ gcloud compute addresses describe <IP_NAME> --global
GKEで組んだクラスタへのHTTPS接続を試みる
GKEではGAEやAWSのロードバランサーのようにボタンポチーでHTTPS対応!みたいにはいかないようなのでメモ。
単純に実現しようとすると cert-manager でLet's Encryptの証明書を取得するのが最も手っ取り早そうなので、これを試してみることにします。
ちなみに 公式ドキュメント まんまなのでこっちを見たほうが早いと思います。
Helmのインストール
HelmはKubernetesのパッケージマネージャーです。
Macなら brew install kubernetes-helm
で入る。
スクリプト叩くだけでもいい。詳しくは こちら。
サービスアカウントの作成
サービスアカウントを作る
$ kubectl create serviceaccount tiller --namespace=kube-system
作成したサービスアカウントに権限をつける
$ kubectl create clusterrolebinding tiller-admin --serviceaccount=kube-system:tiller --clusterrole=cluster-admin
この例では管理者権限を付与してますが本番環境で使うときはしっかり権限のハンドリングをしておいたほうが良さそうです。参考
Tillerのインストール
サービスアカウントを渡してhelmをイニシャライズします。
これでKubernetesクラスタにTillerが立ちます。
Tillerはhelmのクライアントから送られてくる情報を元にうまいことk8sのAPIを叩いてくれるいい奴です。
$ helm init --service-account=tiller
nginx-ingressを立てる
$ helm install stable/nginx-ingress
これで各クラウドベンダのロードバランサーも立ちます。お金がかかります。しばらくすると外部IPが生えます。
外部IPにドメインを紐付ける
DNSの設定を行う。
アプリケーションのデプロイ
アプリケーションのServiceを立てておく。
apiVersion: extensions/v1beta1 kind: Ingress metadata: name: exmaple-ingress annotations: kubernetes.io/ingress.class: "nginx" # certmanager.k8s.io/issuer: "letsencrypt-staging" # certmanager.k8s.io/acme-challenge-type: http01 spec: tls: - hosts: - example.example.com secretName: example-tls rules: - host: example.example.com # ドメイン名 http: paths: - path: / backend: serviceName: example-service # アプリケーションのService名 servicePort: 80
これを適当なファイル(今回はingress.yml
)に保存して
$ kubectl apply -f ingress.yml
ここまでで外部からの通信が可能になる。
cert-managerのインストール
$ kubectl apply -f https://raw.githubusercontent.com/jetstack/cert-manager/release-0.6/deploy/manifests/00-crds.yaml
Issuer の登録
Let's Encryptには証明書の取得制限があります。
基本的に気にしなくてもいいレベルの制限ですが、同じドメインの証明書の取得は一週間に5度までです。
作り直したりする過程で制限に入っても困るのでstagingで試してからにします。
なお、DNS-01方式はだるそうだったので、HTTP-01方式でドメインの認証を行います。
apiVersion: certmanager.k8s.io/v1alpha1 kind: Issuer metadata: name: letsencrypt-staging spec: acme: # The ACME server URL server: https://acme-staging-v02.api.letsencrypt.org/directory # Email address used for ACME registration email: user@example.com # Name of a secret used to store the ACME account private key privateKeySecretRef: name: letsencrypt-staging # Enable the HTTP-01 challenge provider http01: {}
$ kubectl apply -f staging-issuer.yaml
本番用のIssuerも作成しておく。
apiVersion: certmanager.k8s.io/v1alpha1 kind: Issuer metadata: name: letsencrypt-prod spec: acme: # The ACME server URL server: https://acme-v02.api.letsencrypt.org/directory # Email address used for ACME registration email: user@example.com # Name of a secret used to store the ACME account private key privateKeySecretRef: name: letsencrypt-prod # Enable the HTTP-01 challenge provider http01: {}
$ kubectl apply -f production-issuer.yaml
再度ingress.ymlを開いてコメントアウトを外した後にapplyする。
apiVersion: extensions/v1beta1 kind: Ingress metadata: name: exmaple-ingress annotations: kubernetes.io/ingress.class: "nginx" certmanager.k8s.io/issuer: "letsencrypt-staging" certmanager.k8s.io/acme-challenge-type: http01 spec: tls: - hosts: - example.example.com secretName: example-tls rules: - host: example.example.com http: paths: - path: / backend: serviceName: example-service servicePort: 80
$ kubectl apply -f ingress.yml
applyすることでOrderが動いてLet's Encryptに対してリクエストが飛びます。
証明書の発行状態は kubectl describe certificate example-tls
で表示されるEventから見れる。
完了していた場合はSecretが作成されている。
$ kubectl describe secret example-tls
で確認できる。
問題なければIssuerを本番のものに切り替える。
apiVersion: extensions/v1beta1 kind: Ingress metadata: name: exmaple-ingress annotations: kubernetes.io/ingress.class: "nginx" certmanager.k8s.io/issuer: "letsencrypt-prod" # changed certmanager.k8s.io/acme-challenge-type: http01 spec: tls: - hosts: - example.example.com secretName: example-tls rules: - host: example.example.com http: paths: - path: / backend: serviceName: example-service servicePort: 80
$ kubectl apply -f ingress.yml
Issuerを変更した後に、Secretを削除することで変更後のIssuerで証明書を取得します。
$ kubectl delete secret example-tls
ちょっとだけ時間がかかる。 状態はOrderから見れる。
$ kubectl get order NAME AGE example-tls-2000613234 2h
$ kubectl describe order example-tls-2000613234
以上でHTTPS接続が確認できた。
Helmとか知ってればあんまり手間じゃないのかもしれないけど結構面倒くさかったです…