ビビリフクロウの足跡

いつもお世話になっているインターネットへの恩返し

自作の量子コンピュータシミュレータ

インフラ畑出身の私ですが、最近はpython量子コンピュータシミュレータなんかを書いています。

pythonでコード書いたり、テスト書いたりする練習になるのと、単純に量子情報の研究やっていたので、気になっていたからですね。ですが、単純にアプリケーションに対する考え方が不足していてこれまで真剣に手を出そうとはしていませんでした。業務を通じてこれだけ考え方が醸成できたので、職場の先輩方には感謝感謝です。。。!

実際の開発コードですが、ここで開発しています。

https://github.com/bbrfkr/quantum-simulator

量子コンピュータというと敷居が高そうに感じますが、コアコンセプトとして、量子ビットと時間発展、観測量、結合系の作り方を飲み込めれば、物理学出身でなくても理解することができると思います。↑の開発コードではこのコアコンセプトを数学的に愚直に実装しています。。。

内部的にnumpyを使っているので、できるならば(よく知らないので教えて欲しい。。。)TensorFlowやPyTorchなどの機械学習ライブラリを使って高速化&GPU活用をしてみたいなーと夢膨らませたり。。。

これでKubeFlowとか使えたら、CNDTのネタになったりしないかなー。。。難しそうですけど。

もし筆を動かす気になれば今後、コアコンセプトである量子ビット、時間発展、観測量、結合系について解説していければと思います!本日はこの辺で。。。

Hashicorp Vault 触ってみた

前々からHashicorp製品群には興味がありましたが、試そう、試そうと思うだけで中々本格的に手を出せずにいました。なんというか、Hashicorp製品群を本格的に利用しようという考えにまでならないんですよね。。。

Vagrant知ったときはインフラしかできなかったので、「仮想マシン自分で作ればいいじゃない」となってしまい、Terraformに興味を持ったときはOpenStackやAWSにどっぷりだったので、「CloudFormationやHeatでいいじゃない」となってました。NomadやConsulなんかもありますが、コンテナオーケストレータとサービスメッシュはKubernetesとIstion & Linkerdが押さえている状況なので、これも必要に迫られて、というのは中々なさそう。

その中で最近唯一本格的に触ってみたい、触ってみなければ! と思っているのがVaultです。

なんでVaultかというと、自宅プライベートクラウドを作ろうとしたときに各コンポーネントをコンテナで立てるのは良いのですが、コンテナを実行する基盤自体は仮想マシン、もしくは物理マシンになります。OSレイヤ以下をメンテナンスしなくてはならないので構築、運用負荷がそれなりに高いわけですが、なるべく負荷を下げるためにInfrastructure as Codeを積極的に実践していこうと思っています。そうするとコードに機密情報を与えるにはどうすればよいか。。。となったわけです。

クラウド上であればAWS Secrets ManagerやAWS KMSなどのシークレット管理サービスが存在するのでいいのですが、非クラウド環境においては個別にそれらに対応するソフトウェアを探すか、手動に甘んじるしかありません。そこでクラウドネイティブなシークレット管理ソフトウェアを探して真っ先にたどり着いたのがHashicorp Vaultだったということです。

今回は月並みに「触ってみた」ということで、以下の流れで簡単にCLIでシークレットを取得するまでを見ていきます。

  • サーバ構築 - Seal/Unseal
  • シークレットの登録
  • Policyの設定
  • ユーザの設定
  • ログイン - シークレット取得

サーバ構築

構築と言っても公式でdockerコンテナが用意されているので、基本的にはconfigファイルを作成して、server 引数をつけてコンテナを起動するだけです。しかし、Vaultはただ引数を指定して、サーバを初期化しただけではいきなり使えるようにはなりません。使える状態にするためにはUnsealという作業が必要になります。

Vautlは起動直後(再起動時も含む)またはSeal操作後にSeal状態となります。これはシークレットがどこに保存されているかはわかっているが、暗号化されたシークレットを見る、つまり復号化する手段を知らない状態です。したがって、初期化時に生成された復号化するためのキーを閾値の数まで入力して、復号化に必要なマスターキーをVaultに持たせてやる必要があります。

初期化とUnsealを一括して実行できるpythonスクリプトとセットでリポジトリを作成しましたので、よろしければご利用ください。なお検証のため、APIはhttp通信を前提にしておりますので、検証でのご利用か、必要に応じてhttpsに変更してご利用くださいませ。

ちなみにSeal状態の画面をパシャリ。

f:id:bbrfkr:20200419173228p:plain

シークレットの登録

さて、サーバを起動してUnseal状態になったら、secrets/secrets.yaml に書かれた root_token を使ってログインします。

f:id:bbrfkr:20200419173457p:plain

ログインするとこんな画面になるので、早速Key-Valueなシークレットを作っていきましょう!

f:id:bbrfkr:20200419173606p:plain f:id:bbrfkr:20200419173616p:plain f:id:bbrfkr:20200419173651p:plain f:id:bbrfkr:20200419173734p:plain

適当に test-secrets というシークレット名で password: p@ssw0rd という不届きなKey-Valueを登録してみます。

f:id:bbrfkr:20200419173856p:plain f:id:bbrfkr:20200419173915p:plain

これでOK!

Policyの設定

次にPolicyを作成していきます。VautlにおけるPolicyは今の私の理解だとIAM Policyのようなもので、どのシークレット(厳密にはAPIサーバのURLパス)をCRUDできるのかを決めるものです。Policiesメニューから作っていきましょう。

f:id:bbrfkr:20200419174210p:plain

今回は先程作った test-secrets シークレットを読み込めるだけのポリシー test-policy を作ってみます。

f:id:bbrfkr:20200419174314p:plain f:id:bbrfkr:20200419174327p:plain

はい。

ユーザの設定

PolicyはIAMポリシーのようなものといいました。次はポリシーを割り当てるユーザ、IAMユーザのようなものを作っていきます。Accessメニューから行きましょう。

f:id:bbrfkr:20200419174517p:plain f:id:bbrfkr:20200419174545p:plain f:id:bbrfkr:20200419174607p:plain f:id:bbrfkr:20200419174619p:plain

雑に id = test-user, password = test なユーザを作ります。

f:id:bbrfkr:20200419174728p:plain

Policyとユーザとの紐付けは Generated Token's Policies で行います。

f:id:bbrfkr:20200419174816p:plain f:id:bbrfkr:20200419174832p:plain

できました!

ログイン - シークレット取得

これで準備万端(?)です。CLI経由で用意した test-usertest-secrets の中身を見てみます。まずはログイン。

f:id:bbrfkr:20200419180218p:plain

ログインできたらtokenがVaultから渡されるので、VAULT_TOKEN 環境変数にこれをセットして、kv get コマンドを実行します。

f:id:bbrfkr:20200419180406p:plain

ただ p@ssw0rd が取れただけだけどすごく嬉しい。

ちなみに、-format オプションで出力をjsonにできたりもするので、jq ヘビーユーザも大歓喜ですね!

f:id:bbrfkr:20200419180510p:plain

おわりに

一通りVaultの User & Password での使い方を見てきました。他にもApplicationに組み込む形に最適化された AppRole という認証方式や、Key-Valueだけではなくて PKIシークレットなんかも管理できます。またAWXではサーバへの認証キーをVautl側で管理できたり、KubernetesでもAgent-injectorなんてものがあったりと、シークレット情報をVaultで一元管理するために役立つ公式 & 3rd Partyツールが賑わっているようです! この記事をご覧になって興味が湧いた方はまずは以下のリポジトリで体験いただければ幸いです。

自宅プライベートクラウド再構想

生きています! 過去の自分よ、ごめんなさい。。。もうそれ以上は言うまい。。。

さて、自宅プライベートクラウド環境ですが、構成管理ツールで構築・運用を自動化しているものの、やはりVMに直接ソフトウェアをインストールするとなるとペット的に扱わざるを得ないので、アップデートしなくなり塩漬けになる、という問題をはらんでいました。OpenStackは半年でアップデートされるので、もう少し気軽に移行できるようにしておきたい。。。

そう思い立ち、全てのコンポーネントKubernetes上でコンテナとして扱うことにしました。Yahoo!さんがやっているようなOpenStack on Kubernetesですね。これを自宅でも実現したいということです。今回はこの構想について紹介したいと思います。なお、Keystone、Swift、Glance、Nova、Neutron、Horizonまでは検証済みで、動いた実績のある構想です。

物理構成

物理構成は以下の図のとおりです。8台の物理サーバと1台の1G L2SW、1台の10G HUBを用意しています。図を見ていただければ解ると思いますので、特筆することは無いです。機械学習などの重い処理を動かすことは想定していないので、CPUに関してはこだわりなく、非力なものを使っています。メモリ特化です!

f:id:bbrfkr:20200407091909p:plain

仮想構成

仮想的な構成は以下の図のとおりです。仮想的な構成とはここではVMまでに拡張した構成を表すとします。

KVMホスト表記されている1台はNATルータとAWX&Vaultサーバに分割し、残った3台はKubernetes Master・Worker、etcdと負荷分散用のLBを入れ込んでKubernetesを稼働かつ、OpenStackのコントローラコンポーネント群を動かします。

Nova-ComputeホストおよびCeph-OSDホストはベアメタルでkubeletを載せて、Kubernetes Workerノードとしてそれぞれの役割で利用します。

f:id:bbrfkr:20200407091921p:plain

コンポーネント相関

プライベートクラウドを構成する各要素の関係も図に示してみました。今回、先程からCeph-OSDと言っているようにストレージとしてはCephを用います。Cephクラスタは自前でCephコンテナイメージを作ってたててもいいですが、CNCF Incubating ProjectとしてコミュニティマネージドなCephオーケストレーションツール(Cephだけではないですが。。。)であるRookを使い、省力化しています。基本的にコンピュートは冗長構成を組みますが、リソースの関係上冗長化することができない、しても無意味なところ(NATルータなど。2プロバイダ契約ではないので、冗長化しても効果が薄い)はSPOFのままです。一人でこれだけの量のコンポーネントを見ることになるのでEFKスタックとPrometheus&Grafanaを使ってログ収集・可視化、メトリクス監視・可視化はしっかり行っていきます。。。

f:id:bbrfkr:20200407092859p:plain

ベストエフォートで今後、Keystone以降の組み方も記事を書いていきたいと思います。。。

EKS for Fargateの検証しました!

お久しぶりです…! 生きております…!

激動の10,11月だったため、更新が滞っておりました。来年こそはもっと密に更新していきたい…!

さて、裏では12/5にGAされたEKS for Fargateの検証をしておりました。あとは業務でDjangoいじったり、Lambda関数作ったり…

その時の記事がこちらのアドベントカレンダーで公開されておりますので、よろしければご覧いただき、「いいね!」いただけると泣いて喜びます!!

EKS for Fargate the Hard Way 〜 EKS for Fargateをeksctlに頼らず構築する

ちなみに、明日もう一個記事を公開します…!

ElastAlertでEFKスタックで収集したログを監視する

お久しぶりです。生きております…!

最近はコンテナ基盤の監視やログ収集について勉強しておりました。定番のPrometheus+GrafanaやEFKスタックについてですね。前者ではcadvisor、node-exporterを使った一般的なDockerホスト監視やbackbox-exporterを使った外形監視、後者ではfluentdの設定ファイルの記述方法を勉強してコンテナやsystemdサービス、kernelログを収集する方法について学びました。

ところでEFKスタックでログ収集をしたら、ERRORFAILなどの文字列を検出してSlack等に通知したくなりますよね…? Elastic製品ではWatherという、x-pack(https://www.elastic.co/guide/jp/x-pack/current/index.html)内のコンポーネントを使ってこれを実現できるそうですが、x-packは有償なんです… OSSだけで同じことをやろうとしたら、KibanaをやめてGrafanaを使うという手もありますが、ログの検索/一覧性でGrafanaはKibanaには勝てない…

呆然とさまよっていたところで見つけたのがElastAlertというOSSです! ElastAlertを使うとPrometheusのAlertManagerのようにYAMLベースで監視ルールを書いて、Slackを始めとした様々な媒体に通知することができます。またコンテナイメージKibanaのプラグインがbitsensor社によって提供されているため、導入も簡単です。

使い方はbitsensor社のGitHubリポジトリ上の説明が丁寧なので全ては記載しませんが、概ね以下のように進めればOKです。

  1. elastalertのコンフィグファイル(json)を作成する。
  2. kibanaにelastalertプラグインをインストール
  3. EFKスタックを起動
  4. elastalertを起動

監視ルールはKibana上にElastAlertという項目が追加されているので、Creat ruleからエディタを起動し、以下のようなルールを書きます。

es_host: <Elasticsearchのホスト名/IPアドレス>
es_port: 9200

name: Alert on any error

index: logstash-*
timestamp_field: "@timestamp"

type: any

filter:
- query:
    query_string:
      query: "message:/ERROR/i"

alert_subject: "Error!!"

alert_text_type: alert_text_only
alert_text: "occur error on some components!"

alert:
  - slack

slack_webhook_url: "https://hooks.slack.com/services/XXXXX/YYYYY/ZZZZZ"

Test ボタンで試験的にqueryを発行し、アラートを飛ばすことができます。意図したとおりであればSaveをクリックして、アラートを保存して完了です。

kata containers + firecrackerを試してみた

コンテナのセキュリティレベルを上げるためにホストOSとの分離レベルをあげようというモチベーションで開発されたプロダクトがいくつかあります。gVisorやrunV、kata containersなどがそれです。今回はその中のkata containersを検証してみました。kata containersはfirecrackerというAWSがFargateやLambdaの実行環境として利用しているVMMを利用することもできるので、合わせて使ってみたいと思います。

前提

  • Ubuntu 18.04 LTS のCloud image

検証手順

まず、docker ceをインストールします。kata containersのマニュアルに従い、バージョンは最新ではなく、18.06を用います。

sudo apt-get update
sudo apt-get -y install apt-transport-https \
                        ca-certificates \
                        curl \
                        software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository \
     "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
     $(lsb_release -cs) \
     stable"
sudo apt-get install docker-ce=18.06.3~ce~3-0~ubuntu

次にkata containersとfirecrackerのバイナリをDLし、展開します。

wget https://github.com/kata-containers/runtime/releases/download/1.5.0/kata-static-1.5.0-x86_64.tar.xz
sudo tar -xvf kata-static-1.5.0-x86_64.tar.xz -C /

Dockerのデフォルトruntimeの設定をkata containers + firecrackerとします。

sudo su -
cat <<EOF > /etc/docker/daemon.json
{
  "default-runtime": "kata-fc",
  "runtimes": {
    "kata-fc": {
      "path": "/opt/kata/bin/kata-fc"
    }
  },
  "mtu": 1450,
  "storage-driver": "devicemapper"
}
EOF
exit
sudo systemctl daemon-reload
sudo systemctl restart docker

最後に必須カーネルモジュールの読み込み設定をしてインストールは完了です。

sudo su -
cat <<EOF > /etc/modules-load.d/vhost_vsock.conf
vhost_vsock
EOF
exit
sudo modprobe vhost_vsock

ではnginxを起動して、ホストからnginxプロセスが見えるか確かめてみましょう。

sudo docker run -d nginx
sudo ps -ef | grep nginx

grepコマンドの結果のみになっているはずです。ちなみに、containerdで同じことをやると以下のようになりました。

root      8216  8194  0 22:02 ?        00:00:00 nginx: master process nginx -g daemon off;
systemd+  8275  8216  0 22:02 ?        00:00:00 nginx: worker process
tatsuno+  8278  5237  0 22:02 pts/0    00:00:00 grep --color=auto nginx

StatefulSetのノード障害時の挙動と対応

前回、Kubernetesのノード障害時の挙動について記事を書きました。このときはDeploymentを使って挙動を確かめていたですが、前回の記事を見た方から「StatefulSetだと挙動が違ったはず」とのご意見を頂戴しました。

https://twitter.com/tzkb/status/1116597587186814977

そこでStatefulSetだとノード障害時、どんな挙動になるか試してみました。

クラスタの状態は前回の状態からスタートします。

# kubectl get node
NAME                                 STATUS   ROLES    AGE     VERSION
test-cluster-7ajeznlcoium-master-0   Ready    master   2d22h   v1.14.0
test-cluster-7ajeznlcoium-minion-0   Ready    <none>   2d22h   v1.14.0
test-cluster-7ajeznlcoium-minion-1   Ready    <none>   45h     v1.14.0
test-cluster-7ajeznlcoium-minion-2   Ready    <none>   2d16h   v1.14.0

今回はElasticSearchのステートフルセットをデプロイしてみます。KubernetesのGitリポジトリをクローンします。

# cd ~
# git clone https://github.com/kubernetes/kubernetes

Cluster Add-Onからfluentd-elasticsearchディレクトリに入ります。

# cd kubernetes/cluster/addons/fluentd-elasticsearchds

デフォルトではElasticsearchのStatefulSetはvolumeClaimTemplatesを利用するように書かれていないので、以下のようにes-statefulset.yamlを書き換えます。

# Elasticsearch deployment itself
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: elasticsearch-logging
  namespace: kube-system
  labels:
    k8s-app: elasticsearch-logging
    version: v6.6.1
    kubernetes.io/cluster-service: "true"
    addonmanager.kubernetes.io/mode: Reconcile
spec:
  serviceName: elasticsearch-logging
  replicas: 2
  selector:
    matchLabels:
      k8s-app: elasticsearch-logging
      version: v6.6.1
  template:
    metadata:
      labels:
        k8s-app: elasticsearch-logging
        version: v6.6.1
        kubernetes.io/cluster-service: "true"
    spec:
      serviceAccountName: elasticsearch-logging
      containers:
      - image: gcr.io/fluentd-elasticsearch/elasticsearch:v6.6.1
        name: elasticsearch-logging
        resources:
          # need more cpu upon initialization, therefore burstable class
          limits:
            cpu: 1000m
          requests:
            cpu: 100m
        ports:
        - containerPort: 9200
          name: db
          protocol: TCP
        - containerPort: 9300
          name: transport
          protocol: TCP
        volumeMounts:
        - name: elasticsearch-logging
          mountPath: /data
        env:
        - name: "NAMESPACE"
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
      # Elasticsearch requires vm.max_map_count to be at least 262144.
      # If your OS already sets up this number to a higher value, feel free
      # to remove this init container.
      initContainers:
      - image: alpine:3.6
        command: ["/sbin/sysctl", "-w", "vm.max_map_count=262144"]
        name: elasticsearch-logging-init
        securityContext:
          privileged: true
  volumeClaimTemplates:
  - metadata:
      name: elasticsearch-logging
    spec:
      accessModes:
      - ReadWriteOnce
      resources:
        requests:
          storage: 10Gi

ではおもむろにデプロイしてみます。

# kubectl apply -f es-statefulset.yaml
# kubectl get pod -n kube-system -o wide -l k8s-app=elasticsearch-logging
NAME                      READY   STATUS    RESTARTS   AGE     IP             NODE                                 NOMINATED NODE   READINESS GATES
elasticsearch-logging-0   1/1     Running   0          3m34s   10.100.2.221   test-cluster-7ajeznlcoium-minion-2   <none>           <none>
elasticsearch-logging-1   1/1     Running   0          99s     10.100.0.97    test-cluster-7ajeznlcoium-minion-0   <none>           <none>

ここでtest-cluster-7ajeznlcoium-minion-2のkubeletを停止します。

(test-cluster-7ajeznlcoium-minion-2)
# sudo systemctl stop kubelet

すると。。。

# kubectl get pod -n kube-system -o wide -l k8s-app=elasticsearch-logging
NAME                      READY   STATUS        RESTARTS   AGE     IP             NODE                                 NOMINATED NODE   READINESS GATES
elasticsearch-logging-0   1/1     Terminating   0          11m     10.100.2.221   test-cluster-7ajeznlcoium-minion-2   <none>           <none>
elasticsearch-logging-1   1/1     Running       0          9m56s   10.100.0.97    test-cluster-7ajeznlcoium-minion-0   <none>           <none>

StatefulSetの場合は、PodのTerminate処理が完了してから新しいPodが上がるので、kubeletが反応しない以上Terminate処理が完了できず、stuckしてしまうみたいです。

ではどうするか。。。その際の解の一つがTwitter上でご指摘いただいた方から教わったkube-fencingです。

kube-fencingを使うと、NotReadyなノードが生じた場合、その上のk8sオブジェクトを全て削除してノードをフェンシング(強制再起動)してくれます。k8sオブジェクトの削除対象にはNodeオブジェクトも含まれるので、その上に乗ったPodはTerminateどころかetcdから消えます。したがって新しいPodが間違いなく作られるという仕組みで回避しようというのです。

では早速インストールしてみましょう。ソースコードをDLします。

# cd ~
# git clone https://github.com/kvaps/kube-fencing

manifest群が置いてあるディレクトリに移動します。

# cd kube-fencing/examples

私が管理しているノード群はOpenStack上に乗っているので、openstackコマンドを使ってfencingを実現したいと思います。以下のようにfencing-script.yamlを書き換えます。

apiVersion: v1
kind: ConfigMap
metadata:
  name: fencing-scripts
  namespace: fencing
data:
  fence.sh: |
    #!/bin/bash
    NODE="$1"
    source /openstack/openrc-file
    openstack server reboot --hard $1

fencing-scriptに与えるopenrcファイルを用意し、secretオブジェクトを作成します。Secretオブジェクトを作るために、fencingNamespaceも一緒に作ります。

# kubectl create ns fencing
# kubectl create secret generic openrc-file --from-file=openrc-file=openrc-file -n fencing

作ったSecretをマウントするように、またimageもopenstackコマンドがインストールされたものを指定するようにfencing-agents.yamlを書き換えます。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: fencing-agents
  namespace: fencing
spec:
  replicas: 1
  selector:
    matchLabels:
      app: fencing-agents
  template:
    metadata:
      labels:
        app: fencing-agents
    spec:
      nodeSelector:
        node-role.kubernetes.io/master: ""
      tolerations:
      - key: node-role.kubernetes.io/master
        operator: Exists
        effect: NoSchedule
      hostNetwork: true
      containers:
      - name: fencing-agents
        image: bbrfkr0129/kube-fencing-agents
        command: [ "/usr/bin/tail", "-f", "/dev/null" ]
        volumeMounts:
        - name: scripts
          mountPath: /scripts
        - name: openrc-file
          mountPath: /openstack
      volumes:
      - name: scripts
        configMap:
          name: fencing-scripts
          defaultMode: 0744
      - name: openrc-file
        secret:
          secretName: openrc-file
          defaultMode: 0400

manifestを適用します。

# kubectl apply -f .

ノードがNotReady時にfencingしてほしいノードはlabel付けをしておく必要があります。ワーカーノードをラベル付けしておきます。

# kubectl label node test-cluster-7ajeznlcoium-minion-0 fencing=enabled
# kubectl label node test-cluster-7ajeznlcoium-minion-1 fencing=enabled
# kubectl label node test-cluster-7ajeznlcoium-minion-2 fencing=enabled

これでインストール完了です! 早速kubeletを停止させてみましょう。

(test-cluster-7ajeznlcoium-minion-2)
# sudo systemctl stop kubelet

どうなるかというと。。。今度は新しいPodが上がってきません。。。

NAME                      READY   STATUS     RESTARTS   AGE     IP            NODE                                 NOMINATED NODE   READINESS GATES
elasticsearch-logging-0   1/1     Running    0          3m51s   10.100.0.98   test-cluster-7ajeznlcoium-minion-0   <none>           <none>
elasticsearch-logging-1   0/1     Init:0/1   0          53s     <none>        test-cluster-7ajeznlcoium-minion-0   <none>           <none>

describeでログを見ると当然で、PVが違うインスタンスにアタッチされていて、移動できない旨のメッセージが表示されます。しかもたちの悪いことにノードオブジェクトは削除/再作成が行われているため、自分にPVがアタッチされていることを覚えていません。

ではやはりkube-fencingでもStatefulSetは救えないのか。。。というと、実は最後の砦があります。それは

もう諦めて、StatefulSetにはファイルシステムベース(NFSなど)のPVを使う、ということです。こうすれば、PVの取り合いが起きてもどのノードもPVをアタッチ/マウントできるので問題を回避できます。nfs-clientexternal-storage-provisionerなどを使って、興味がある方は試してみてください。

結論としては

  • kube-fencingでもBlock DeviceベースのStatefulSetは救えない。FileSystemベースなら大丈夫。

ということでした。