ビビリフクロウの足跡

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

Portusでプライベートなコンテナイメージレジストリを立ててみる

Dockerを使う際にまずお世話になるのがパブリックなコンテナイメージレジストリサービスであるDocker Hubかと思うのですが、業務で本格的にDockerを使うとなった際にはクローズドなネットワークセグメントでしかDockerホストを実行できなかったりして、プライベートなイメージレジストリが欲しくなりますよね?

そんなときのためにDocker側でプライベートレジストリサーバ用のコンテナイメージが用意されているのですが、UIがないのでイメージを一覧や削除などの管理をしようとすると、直にAPIを叩かなくてはならず辛みがあります。そこでUI付きのプライベートレジストリを探すと、プライベートGitリポジトリでも有名なGitLabが引っかかるのですが、あれってリポジトリだけでなく、CIやKubernetes連携など、いろいろな機能がついているじゃないですか。そこまで仰々しくしたくないんですよね…

シンプルにレジストリとUIと認証周りの機能がついていればいいのよ! というモチベーションで再度探すと、ありました! PortusというOSSが…!

が、例のごとく構築に少しつまずいたので、構築・設定方法を残しておきたいと思います。今後Portusを構築する人のために、つまづきポイントを共有できればと思います。

Portusとは

SUSEが開発している、プライベートレジストリに認証機能とWeb UIを提供するOSSです。 ざっくりとどんなものか知りたい方はCyberAgentの青山さんが記事にしてくれているので、そちらをご参照。

構築手順

では、本題に入っていきましょう。

前提

まず前提を示していきますが、以下のとおりです。

  • OS: CentOS 7
  • IP: 192.168.0.100
  • 作業ユーザ: root
  • 構築方法: docker-composeを利用

PortusもDockerコンテナで動作するので、パッケージの導入手順くらいを置き換えていただければ、Dockerが動作するどんなLinuxでも動作するかと思います。

Dockerのインストール

MinimalのCentOS 7のマシンにログインしたら、まずDockerをインストールします。

yum install -y yum-utils device-mapper-persistent-data lvm2
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
yum install -y docker-ce docker-ce-cli containerd.io

インストールしたらサービスを設定しましょう。

systemctl enable docker
systemctl start docker

docker-composeのインストール

docker-composeを使ってPortusを構築するので、インストールしておきます。

curl -L "https://github.com/docker/compose/releases/download/1.23.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/bin/docker-compose
chmod +x /usr/bin/docker-compose

Gitのインストール

Portusのソースコードの中にサンプルのdocker-compose.ymlが入っています。これをDLするためにGitをインストールします。

yum install -y git

Portusのソースコードを取得

Portusのソースコードを取得します。ソースコード/etc/docker/に置くことにします。

cd /etc/docker
git clone https://github.com/SUSE/Portus.git

今回はVer 2.4をデプロイしたいと思います。

cd Portus
git checkout remotes/origin/v2.4
git checkout -b v2.4

以降の作業は/etc/docker/Portus/example/composeで行いますので、移動しておきます。

cd /etc/docker/Portus/examples/compose

証明書群の作成

PortusのWeb UIおよびプライベートレジストリに使われるSSL証明書を作成します。今回は自己署名認証局(CA)を同ホスト内に作り、証明書を発行します。

まずはCAを作成します。

cd secrets
openssl genrsa -out ca.key 2048
openssl req -x509 -new -nodes -key ca.key -subj "/CN=192.168.0.100" -days 7305 -out ca.crt

次にサーバの秘密鍵を作ります。

openssl genrsa -out portus.key 2048

CSRを発行して、CAで署名付き証明書を作ります。

cat <<EOF > csr.conf
[ req ]
default_bits = 2048
prompt = no
default_md = sha256
req_extensions = req_ext
distinguished_name = dn

[ dn ]
C = JP
ST = Tokyo
L = Shinjuku
#O = <organization>
#OU = <organization unit>
CN = 192.168.0.100

[ req_ext ]
subjectAltName = @alt_names

[ alt_names ]
IP.1 = 192.168.0.100

[ v3_ext ]
authorityKeyIdentifier=keyid,issuer:always
basicConstraints=CA:FALSE
keyUsage=keyEncipherment,dataEncipherment
extendedKeyUsage=serverAuth,clientAuth
subjectAltName=@alt_names
EOF

openssl req -new -key portus.key -out portus.csr -config csr.conf
openssl x509 -req -in portus.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out portus.crt -days 10000 -extensions v3_ext -extfile csr.conf
cd ..

.envファイルの修正

docker-composeに読み込ませる.envファイルを修正します。

cat <<EOF > .env
MACHINE_FQDN=192.168.0.100

SECRET_KEY_BASE=$(openssl rand -hex 64)
PORTUS_PASSWORD=password
DATABASE_PASSWORD=p@ssw0rd
EOF

nginx.confの修正

nginxの設定ファイルnginx.conf内のserver_nameパラメータを修正します。

sed -i 's/server_name.*/server_name 192.168.0.100;/g' nginx/nginx.conf

initスクリプトの修正

今回作成したCAをプライベートレジストリに信頼させるため、以下の記述をregistry/initに追加します。

#!/bin/sh

set -x

cp /secrets/portus.crt /usr/local/share/ca-certificates
# here!!
cp /secrets/ca.crt /usr/local/share/ca-certificates

update-ca-certificates
registry serve /etc/docker/registry/config.yml

Portusの起動

あとはdocker-composeで起動するだけです。

docker-compose up -d

以上で、構築は完了です。あとはPortus上で初期設定をしていきましょう。

設定手順

Portus上で設定を行うためにhttps://192.168.0.100にアクセスしましょう!

管理ユーザ設定

まずは管理ユーザであるadminユーザを作ります。

f:id:bbrfkr:20190202203426p:plain

レジストリサーバ登録

レジストリサーバ登録をして…

f:id:bbrfkr:20190202203457p:plain

ユーザ作成

一般ユーザbbrfkrを作ります。

f:id:bbrfkr:20190202203511p:plain f:id:bbrfkr:20190202203540p:plain

再ログイン

そして再ログイン。

f:id:bbrfkr:20190202203553p:plain f:id:bbrfkr:20190202203605p:plain f:id:bbrfkr:20190202203636p:plain

実際にイメージをプッシュしてみる

作ったレジストリサーバにログインするためにはCA証明書をクライアントマシンの適切な場所に置く必要があります。以下の場所に置きましょう。

/etc/docker/certs.d/192.168.0.100/ca.crt

その後レジストリサーバにログインして、nginx:latestをプライベートレジストリにプッシュしてみます。

docker login 192.168.0.100
docker pull nginx:latest
docker tag nginx:latest 192.168.0.100/bbrfkr/nginx:v1.0
docker push 192.168.0.100/bbrfkr/nginx:v1.0

プッシュが成功すると、Portus上で確認できるようになります。

f:id:bbrfkr:20190202203652p:plain

KubeVirtを使ってみる

KubeVirtをお試ししてみたので、その際のメモを残しておきます。

KubeVirtとは

KubeVirtはRed Hat社が主に開発している、CNCF MemberプロダクトのOSSです。KubeVirtを使うとKubernetes上で仮想マシンを管理することができるようになります。これによりコンテナよりも隔離レベルの高い仮想マシンを使いながら、KubernetesのCloud Nativeな機能であるPodネットワーク、Service、サービスディスカバリを仮想マシンに対し適用することができるようになります。

KubeVirtのインストール

インストールは至極簡単です。Kubernetesクラスタを用意したら、以下のコマンドを実行するだけです。

export VERSION=v0.11.0
kubectl create -f https://github.com/kubevirt/kubevirt/releases/download/$VERSION/kubevirt.yaml

もし、Workerノードが仮想化支援機構(IntelでいうVT-x)に対応しておらず、ハードウェアエミュレーションを使いたい場合は、事前に以下のコマンドを実行します。

kubectl create configmap -n kube-system kubevirt-config --from-literal debug.useEmulation=true

KubeVirtを使ってみる

KubeVirtで仮想マシンを操作するには、専用のCLIツールであるvirtctlがあると便利です。virtctlをインストールしましょう。

curl -L -o virtctl https://github.com/kubevirt/kubevirt/releases/download/$VERSION/virtctl-$VERSION-linux-amd64
chmod +x virtctl
sudo chown root:root virtctl
sudo mv virtctl /usr/bin

インストールしたらまずは超軽量OSであるCirrOSを起動してみましょう。仮想マシンオブジェクトをKubernetes上に作成します。

kubectl apply -f https://raw.githubusercontent.com/kubevirt/demo/master/manifests/vm.yaml

kubectl get vmコマンドを実行すると、以下のような出力が得られると思います。

NAME     AGE   RUNNING   VOLUME
testvm   6s    false

RUNNIGfalseとあるように、この時点ではまだ仮想マシンは起動していません。起動にはvirtctlコマンドを使って以下のようにします。

virtctl start testvm

すると、RUNNINGtrueに変わります。

NAME     AGE   RUNNING   VOLUME
testvm   1m    true

実は仮想マシンオブジェクトはコンテナで言うReplicaSetのようなもので、仮想マシンの起動定義情報が格納されているだけです。実際の仮想マシンのポインタ情報は「仮想マシンインスタンスオブジェクト」に格納されています。仮想マシンインスタンスオブジェクトを一覧するにはkubectl get vmiコマンドを実行します。

NAME     AGE   PHASE     IP           NODENAME
testvm   3m    Running   10.100.0.6   kubevirt-test-jbsl2irizcpz-minion-1

すると、上記のように仮想マシンの現在の状態やIP情報などを手に入れることができます。

仮想マシンのコンソール画面(CUIベース or GUIベース)にアクセスするには、またvirtctlコマンドを使います。

virtctl console testvm
virtctl vnc testvm

しかし、Cloudイメージを使う場合、通常パスワード認証できないので、これらのコマンドを使う機会は少ないでしょう。

仮想マシンSSHログインするにはコンテナと同様に仮想マシンを Service で外部公開する必要があります。これもvirtctlコマンドで簡単に行なえます。

virtctl expose vm testvm --name testvm --port 22 --type NodePort

kubectl get svc -o wideすると、確かにNodePort Serviceが作成されてますね!

NAME         TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)        AGE   SELECTOR
kubernetes   ClusterIP   10.254.0.1    <none>        443/TCP        18m   <none>
testvm       NodePort    10.254.20.5   <none>        22:30296/TCP   64s   kubevirt.io/domain=testvm,kubevirt.io/size=small

どこかのNodeの30296番ポートをsshで突っついてみましょう。ユーザ名はcirros、パスワードはgocubsgoです。

ssh -p 30296 cirros@<NodeのIP>
$

無事ログインできましたか?

仮想マシンの停止にもvirtctlを使います。

virtctl stop testvm 

仮想マシンの削除にはkubectl delete vmコマンドです。これを実行すると該当の仮想マシンインスタンスも一緒に消えます。

kubectl delete vm testvm

最後に

いかがでしたでしょうか。仮想マシンの隔離レベルを担保しなくてはいけないけれど、コンテナは使えないといったケースや、コンテナネットワークに仮想マシンを直接繋いでレイテンシを改善したい場合などに使えそうかなと思っています。 時間を見つけて任意の仮想マシンイメージを起動する方法も書こうと思いますので、お楽しみに!

Kubernetes the Hard Way on OpenStack

Kubernetesの構築をスクラッチで実践できるチュートリアルとしてKubernetes the Hard Wayがあります。来年のはじめくらいにCKAを取得したいと考えているので、本チュートリアルを自宅のOpenStack上で実施してみました。その際の実施手順と少しばかりの解説を残しておきたいと思います。

前提

OpenStackには次のコンポーネントがインストールされているとします。

  • Keystone
  • Glance
  • Nova
  • Neutron
  • Neutron LBaaSv2
  • Designate

次のインスタンスを用意し、HA構成のKubernetesを構成します。 ホスト名でFloatingIPに名前解決ができるようにDesignateを構成しておきます。

  • Masterサーバ x3

    • OS: CentOS 7 Minimal Install
    • vCPU: 2
    • Mem: 4GB
    • ホスト名: hardway-master-[0:2].bbrfkr.mydns.jp
    • IP: 10.0.0.[10:12]
    • FloatingIP: 192.168.0.[10:12]
  • Workerサーバ x3

    • OS: CentOS 7 Minimal Install
    • vCPU: 2
    • Mem: 4GB
    • ホスト名: hardway-worker-[0:2].bbrfkr.mydns.jp
    • IP: 10.0.0.[20:22]
    • FloatingIP: 192.168.0.[20:22]

また、FloatingIPに対して到達可能なネットワーク上に作業用端末を用意しておきます。作業用端末ではあらかじめ以下のコマンドが叩けるように準備しておきます。私の場合はWindows 10にWSLのUbuntuをインストールして、その上から叩けるようにしました。

  • cfssl
  • cfssljson
  • kubectl
  • openstack
  • neutron

Masterサーバのkube-apiserverに対するエンドポイントとしてNeutron LBaaSv2にてロードバランサを切っておきます。また、VIP portに対するセキュリティグループも適切に設定しておきましょう。

  • Neutron LBaaSv2 LB
    • 名前: kubernetes-the-hard-way
    • Internal VIP: 10.0.0.100
    • FloatingIP: 192.168.0.100
    • リスナーポート: 6443

手順

以降、Hard Wayな手順を示して行きます。作業対象は()付きでmasters、workers、bastion(作業用端末の意)で示していきます。

OS設定

  • (masters, workers) OSパッケージの更新とSELinuxの無効化をしておきます。これはKubernetesのインストール要件になります。
sudo sed -i 's/^SELINUX=.*/SELINUX=permissive/g' /etc/selinux/config
sudo yum update -y
sudo reboot

CAおよび各種証明書の作成

ここではkube-apiserverとhttps通信するために必要な各種証明書を作成していきます。

  • (bastion) CAの署名における設定とCA証明書および鍵を作成します。
cat > ca-config.json <<EOF
{
  "signing": {
    "default": {
      "expiry": "8760h"
    },
    "profiles": {
      "kubernetes": {
        "usages": ["signing", "key encipherment", "server auth", "client auth"],
        "expiry": "8760h"
      }
    }
  }
}
EOF

cat > ca-csr.json <<EOF
{
  "CN": "Kubernetes",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "US",
      "L": "Portland",
      "O": "Kubernetes",
      "OU": "CA",
      "ST": "Oregon"
    }
  ]
}
EOF

cfssl gencert -initca ca-csr.json | cfssljson -bare ca
  • (bastion) Kubernetesクラスタのadminユーザに対するクライアント証明書および鍵を作成します。Kubernetesのx509証明書認証においてはCN(Common Name)に指定した値がクラスタのユーザ名、O(Organization)に指定した値がグループ名として扱われます。system:mastersグループはデフォルトでKubernetesクラスタのフルコントロール権限を持つグループです。
cat > admin-csr.json <<EOF
{
  "CN": "admin",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "US",
      "L": "Portland",
      "O": "system:masters",
      "OU": "Kubernetes The Hard Way",
      "ST": "Oregon"
    }
  ]
}
EOF

cfssl gencert \
  -ca=ca.pem \
  -ca-key=ca-key.pem \
  -config=ca-config.json \
  -profile=kubernetes \
  admin-csr.json | cfssljson -bare admin
  • (bastion) kubeletがkube-apiserverと通信するときのクライアント証明書および鍵を作成します。ここでは厳密にはNode Authorizationというkube-apiserverの認可機能を使うために、CNにsystem:node:<インスタンス名>、Oにsystem:nodesという値を指定するのが肝となります。
for instance in hardway-worker-0 hardway-worker-1 hardway-worker-2; do
cat > ${instance}-csr.json <<EOF
{
  "CN": "system:node:${instance}",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "US",
      "L": "Portland",
      "O": "system:nodes",
      "OU": "Kubernetes The Hard Way",
      "ST": "Oregon"
    }
  ]
}
EOF

EXTERNAL_IP=$(openstack server show -f json ${instance} | \
              jq -r .addresses | \
              awk -F= '{ print $2 }' | \
              sed 's/,//g' | \
              awk '{ print $2 }')

INTERNAL_IP=$(openstack server show -f json ${instance} | \
              jq -r .addresses | \
              awk -F= '{ print $2 }' | \
              sed 's/,//g' | \
              awk '{ print $1 }')

cfssl gencert \
  -ca=ca.pem \
  -ca-key=ca-key.pem \
  -config=ca-config.json \
  -hostname=${instance},${EXTERNAL_IP},${INTERNAL_IP} \
  -profile=kubernetes \
  ${instance}-csr.json | cfssljson -bare ${instance}
done
  • (bastion) kube-controller-managerがkube-apiserverと通信するための証明書および鍵を作成します。
cat > kube-controller-manager-csr.json <<EOF
{
  "CN": "system:kube-controller-manager",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "US",
      "L": "Portland",
      "O": "system:kube-controller-manager",
      "OU": "Kubernetes The Hard Way",
      "ST": "Oregon"
    }
  ]
}
EOF

cfssl gencert \
  -ca=ca.pem \
  -ca-key=ca-key.pem \
  -config=ca-config.json \
  -profile=kubernetes \
  kube-controller-manager-csr.json | cfssljson -bare kube-controller-manager
  • (bastion) kube-proxyがkube-apiserverと通信するための証明書および鍵を作成します。
cat > kube-proxy-csr.json <<EOF
{
  "CN": "system:kube-proxy",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "US",
      "L": "Portland",
      "O": "system:node-proxier",
      "OU": "Kubernetes The Hard Way",
      "ST": "Oregon"
    }
  ]
}
EOF

cfssl gencert \
  -ca=ca.pem \
  -ca-key=ca-key.pem \
  -config=ca-config.json \
  -profile=kubernetes \
  kube-proxy-csr.json | cfssljson -bare kube-proxy
  • (bastion) kube-schedulerがkube-apiserverと通信するための証明書および鍵を作成します。
cat > kube-scheduler-csr.json <<EOF
{
  "CN": "system:kube-scheduler",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "US",
      "L": "Portland",
      "O": "system:kube-scheduler",
      "OU": "Kubernetes The Hard Way",
      "ST": "Oregon"
    }
  ]
}
EOF

cfssl gencert \
  -ca=ca.pem \
  -ca-key=ca-key.pem \
  -config=ca-config.json \
  -profile=kubernetes \
  kube-scheduler-csr.json | cfssljson -bare kube-scheduler
  • (bastion) kube-apiserver用のサーバ証明書および鍵を作成します。この証明書で許可されたCNやSAN(Subject Alternative Name)でアクセスしなければkube-apiservertとhttps通信できないので、登録するSAN(cfssl gencertコマンドの-hostnameオプションのところ)に注意します。
KUBERNETES_PUBLIC_ADDRESS=$(neutron floatingip-list | grep \
                          $(neutron lbaas-loadbalancer-show kubernetes-the-hard-way | \
                            grep vip_port_id | \
                            awk '{ print $4 }') | \
                          awk '{ print $8 }')
KUBERNETES_INTERNAL_ADDRESS=$(neutron lbaas-loadbalancer-show kubernetes-the-hard-way | \
                              grep vip_address | \
                              awk '{ print $4 }')
KUBERNETES_PUBLIC_DNS=kubernetes-the-hard-way.bbrfkr.mydns.jp

cat > kubernetes-csr.json <<EOF
{
  "CN": "kubernetes",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "US",
      "L": "Portland",
      "O": "Kubernetes",
      "OU": "Kubernetes The Hard Way",
      "ST": "Oregon"
    }
  ]
}
EOF

for instance in hardway-master-0 hardway-master-1 hardway-master-2; do
MASTER_INTERNAL_IPS=${MASTER_INTERNAL_IPS}$(openstack server show -f json ${instance} | \
              jq -r .addresses | \
              awk -F= '{ print $2 }' | \
              sed 's/,//g' | \
              awk '{ print $1 }'),
done

cfssl gencert \
  -ca=ca.pem \
  -ca-key=ca-key.pem \
  -config=ca-config.json \
  -hostname=10.32.0.1,${MASTER_INTERNAL_IPS}${KUBERNETES_PUBLIC_ADDRESS},${KUBERNETES_INTERNAL_ADDRESS},${KUBERNETES_PUBLIC_DNS},127.0.0.1,kubernetes.default \
  -profile=kubernetes \
  kubernetes-csr.json | cfssljson -bare kubernetes
  • (bastion) ServiceAccountのトークン発行用キーペアを作成します。
cat > service-account-csr.json <<EOF
{
  "CN": "service-accounts",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "US",
      "L": "Portland",
      "O": "Kubernetes",
      "OU": "Kubernetes The Hard Way",
      "ST": "Oregon"
    }
  ]
}
EOF

cfssl gencert \
  -ca=ca.pem \
  -ca-key=ca-key.pem \
  -config=ca-config.json \
  -profile=kubernetes \
  service-account-csr.json | cfssljson -bare service-account
  • (bastion) 証明書群をMasterサーバ、Workerサーバに配布します。
for instance in hardway-worker-0 hardway-worker-1 hardway-worker-2; do
  scp ca.pem ${instance}-key.pem ${instance}.pem centos@${instance}.bbrfkr.mydns.jp:~/
done

for instance in hardway-master-0 hardway-master-1 hardway-master-2; do
  scp ca.pem ca-key.pem kubernetes-key.pem kubernetes.pem \
    service-account-key.pem service-account.pem centos@${instance}.bbrfkr.mydns.jp:~/
done

kubeconfig群の作成

ここではkube-apiserverと通信するために必要な認証ファイルであるkubeconfigファイル群を作成していきます。

  • (bastion) kubelet用のkubeconfigを作成します。
KUBERNETES_PUBLIC_ADDRESS=$(neutron floatingip-list | grep \
                          $(neutron lbaas-loadbalancer-show kubernetes-the-hard-way | \
                            grep vip_port_id | \
                            awk '{ print $4 }') | \
                          awk '{ print $8 }')

for instance in hardway-worker-0 hardway-worker-1 hardway-worker-2; do
  kubectl config set-cluster kubernetes-the-hard-way \
    --certificate-authority=ca.pem \
    --embed-certs=true \
    --server=https://${KUBERNETES_PUBLIC_ADDRESS}:6443 \
    --kubeconfig=${instance}.kubeconfig

  kubectl config set-credentials system:node:${instance} \
    --client-certificate=${instance}.pem \
    --client-key=${instance}-key.pem \
    --embed-certs=true \
    --kubeconfig=${instance}.kubeconfig

  kubectl config set-context default \
    --cluster=kubernetes-the-hard-way \
    --user=system:node:${instance} \
    --kubeconfig=${instance}.kubeconfig

  kubectl config use-context default --kubeconfig=${instance}.kubeconfig
done
  • (bastion) kube-proxy用のkubeconfigを作成します。
kubectl config set-cluster kubernetes-the-hard-way \
  --certificate-authority=ca.pem \
  --embed-certs=true \
  --server=https://${KUBERNETES_PUBLIC_ADDRESS}:6443 \
  --kubeconfig=kube-proxy.kubeconfig

kubectl config set-credentials system:kube-proxy \
  --client-certificate=kube-proxy.pem \
  --client-key=kube-proxy-key.pem \
  --embed-certs=true \
  --kubeconfig=kube-proxy.kubeconfig

kubectl config set-context default \
  --cluster=kubernetes-the-hard-way \
  --user=system:kube-proxy \
  --kubeconfig=kube-proxy.kubeconfig

kubectl config use-context default --kubeconfig=kube-proxy.kubeconfig
  • (bastion) kube-controller-manager用のkubeconfigを作成します。
kubectl config set-cluster kubernetes-the-hard-way \
  --certificate-authority=ca.pem \
  --embed-certs=true \
  --server=https://127.0.0.1:6443 \
  --kubeconfig=kube-controller-manager.kubeconfig

kubectl config set-credentials system:kube-controller-manager \
  --client-certificate=kube-controller-manager.pem \
  --client-key=kube-controller-manager-key.pem \
  --embed-certs=true \
  --kubeconfig=kube-controller-manager.kubeconfig

kubectl config set-context default \
  --cluster=kubernetes-the-hard-way \
  --user=system:kube-controller-manager \
  --kubeconfig=kube-controller-manager.kubeconfig

kubectl config use-context default --kubeconfig=kube-controller-manager.kubeconfig
  • (bastion) kube-scheduler用のkubeconfigを作成します。
kubectl config set-cluster kubernetes-the-hard-way \
  --certificate-authority=ca.pem \
  --embed-certs=true \
  --server=https://127.0.0.1:6443 \
  --kubeconfig=kube-scheduler.kubeconfig

kubectl config set-credentials system:kube-scheduler \
  --client-certificate=kube-scheduler.pem \
  --client-key=kube-scheduler-key.pem \
  --embed-certs=true \
  --kubeconfig=kube-scheduler.kubeconfig

kubectl config set-context default \
  --cluster=kubernetes-the-hard-way \
  --user=system:kube-scheduler \
  --kubeconfig=kube-scheduler.kubeconfig

kubectl config use-context default --kubeconfig=kube-scheduler.kubeconfig
  • (bastion) adminユーザ用のkubeconfigを作成します。
kubectl config set-cluster kubernetes-the-hard-way \
  --certificate-authority=ca.pem \
  --embed-certs=true \
  --server=https://127.0.0.1:6443 \
  --kubeconfig=admin.kubeconfig

kubectl config set-credentials admin \
  --client-certificate=admin.pem \
  --client-key=admin-key.pem \
  --embed-certs=true \
  --kubeconfig=admin.kubeconfig

kubectl config set-context default \
  --cluster=kubernetes-the-hard-way \
  --user=admin \
  --kubeconfig=admin.kubeconfig

kubectl config use-context default --kubeconfig=admin.kubeconfig
  • (bastion) kubeconfig群をMasterサーバ、Workerサーバに配布します。
for instance in hardway-worker-0 hardway-worker-1 hardway-worker-2; do
  scp ${instance}.kubeconfig kube-proxy.kubeconfig centos@${instance}.bbrfkr.mydns.jp:~/
done

for instance in hardway-master-0 hardway-master-1 hardway-master-2; do
  scp admin.kubeconfig kube-controller-manager.kubeconfig kube-scheduler.kubeconfig centos@${instance}.bbrfkr.mydns.jp:~/
done

データ暗号化用設定ファイルの作成

Kubernetesではetcdにクラスタ情報が格納されますが、ここではその格納データを暗号化するための設定を実施します。

  • (bastion) クラスタデータ暗号化用設定ファイルを作成し、Masterサーバに配布します。
ENCRYPTION_KEY=$(head -c 32 /dev/urandom | base64)
cat > encryption-config.yaml <<EOF
kind: EncryptionConfig
apiVersion: v1
resources:
  - resources:
      - secrets
    providers:
      - aescbc:
          keys:
            - name: key1
              secret: ${ENCRYPTION_KEY}
      - identity: {}
EOF

for instance in hardway-master-0 hardway-master-1 hardway-master-2; do
  scp encryption-config.yaml centos@${instance}.bbrfkr.mydns.jp:~/
done

etcdクラスタの構築

ここではMasterサーバ上でetcdのHAクラスタを構築します。

  • (masters) etcdバイナリのダウンロードと適切な場所への配置を行います。
sudo yum -y install wget
wget "https://github.com/coreos/etcd/releases/download/v3.3.9/etcd-v3.3.9-linux-amd64.tar.gz"

tar -xvf etcd-v3.3.9-linux-amd64.tar.gz
sudo mv etcd-v3.3.9-linux-amd64/etcd* /usr/local/bin/
  • (masters) etcd用のsystemd unitファイルを作成し、サービスを有効化します。
sudo mkdir -p /etc/etcd /var/lib/etcd
sudo cp ca.pem kubernetes-key.pem kubernetes.pem /etc/etcd/

INTERNAL_IP=$(curl http://169.254.169.254/latest/meta-data/local-ipv4)
ETCD_NAME=$(hostname -s)
MASTER_0_HOSTNAME=hardway-master-0
MASTER_0_INTERNAL_IP=10.0.0.10
MASTER_1_HOSTNAME=hardway-master-1
MASTER_1_INTERNAL_IP=10.0.0.11
MASTER_2_HOSTNAME=hardway-master-2
MASTER_2_INTERNAL_IP=10.0.0.12
cat <<EOF | sudo tee /etc/systemd/system/etcd.service
[Unit]
Description=etcd
Documentation=https://github.com/coreos

[Service]
ExecStart=/usr/local/bin/etcd \\
  --name ${ETCD_NAME} \\
  --cert-file=/etc/etcd/kubernetes.pem \\
  --key-file=/etc/etcd/kubernetes-key.pem \\
  --peer-cert-file=/etc/etcd/kubernetes.pem \\
  --peer-key-file=/etc/etcd/kubernetes-key.pem \\
  --trusted-ca-file=/etc/etcd/ca.pem \\
  --peer-trusted-ca-file=/etc/etcd/ca.pem \\
  --peer-client-cert-auth \\
  --client-cert-auth \\
  --initial-advertise-peer-urls https://${INTERNAL_IP}:2380 \\
  --listen-peer-urls https://${INTERNAL_IP}:2380 \\
  --listen-client-urls https://${INTERNAL_IP}:2379,https://127.0.0.1:2379 \\
  --advertise-client-urls https://${INTERNAL_IP}:2379 \\
  --initial-cluster-token etcd-cluster-0 \\
  --initial-cluster ${MASTER_0_HOSTNAME}=https://${MASTER_0_INTERNAL_IP}:2380,${MASTER_1_HOSTNAME}=https://${MASTER_1_INTERNAL_IP}:2380,${MASTER_2_HOSTNAME}=https://${MASTER_2_INTERNAL_IP}:2380 \\
  --initial-cluster-state new \\
  --data-dir=/var/lib/etcd
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable etcd
sudo systemctl start etcd
  • (masters) etcdの正常動作確認をするには以下のコマンドを実行します。
sudo su - -lc \
"ETCDCTL_API=3 etcdctl member list \
--endpoints=https://127.0.0.1:2379 \
--cacert=/etc/etcd/ca.pem \
--cert=/etc/etcd/kubernetes.pem \
--key=/etc/etcd/kubernetes-key.pem"

Masterサーバの構築

ここではMasterサーバの構築を実施します。

sudo mkdir -p /etc/kubernetes/config
  • (masters) Masterサーバ用コンポーネントのバイナリをダウンロードし、適切な場所に配置します。
wget \
"https://storage.googleapis.com/kubernetes-release/release/v1.13.1/bin/linux/amd64/kube-apiserver" \
"https://storage.googleapis.com/kubernetes-release/release/v1.13.1/bin/linux/amd64/kube-controller-manager" \
"https://storage.googleapis.com/kubernetes-release/release/v1.13.1/bin/linux/amd64/kube-scheduler" \
"https://storage.googleapis.com/kubernetes-release/release/v1.13.1/bin/linux/amd64/kubectl"

chmod +x kube-apiserver kube-controller-manager kube-scheduler kubectl
sudo mv kube-apiserver kube-controller-manager kube-scheduler kubectl /usr/local/bin/
  • (masters) kube-apiserver用の証明書群の配置とsystemd unitファイルを作成します。ここでKubernetesのServiceリソースで利用するIP帯10.32.0.0/24を指定します。
sudo mkdir -p /var/lib/kubernetes/
sudo mv ca.pem ca-key.pem kubernetes-key.pem kubernetes.pem \
  service-account-key.pem service-account.pem \
  encryption-config.yaml /var/lib/kubernetes/

INTERNAL_IP=$(curl http://169.254.169.254/latest/meta-data/local-ipv4)
MASTER_0_INTERNAL_IP=10.0.0.10
MASTER_1_INTERNAL_IP=10.0.0.11
MASTER_2_INTERNAL_IP=10.0.0.12

cat <<EOF | sudo tee /etc/systemd/system/kube-apiserver.service
[Unit]
Description=Kubernetes API Server
Documentation=https://github.com/kubernetes/kubernetes

[Service]
ExecStart=/usr/local/bin/kube-apiserver \\
  --advertise-address=${INTERNAL_IP} \\
  --allow-privileged=true \\
  --apiserver-count=3 \\
  --audit-log-maxage=30 \\
  --audit-log-maxbackup=3 \\
  --audit-log-maxsize=100 \\
  --audit-log-path=/var/log/audit.log \\
  --authorization-mode=Node,RBAC \\
  --bind-address=0.0.0.0 \\
  --client-ca-file=/var/lib/kubernetes/ca.pem \\
  --enable-admission-plugins=Initializers,NamespaceLifecycle,NodeRestriction,LimitRanger,ServiceAccount,DefaultStorageClass,ResourceQuota \\
  --enable-swagger-ui=true \\
  --etcd-cafile=/var/lib/kubernetes/ca.pem \\
  --etcd-certfile=/var/lib/kubernetes/kubernetes.pem \\
  --etcd-keyfile=/var/lib/kubernetes/kubernetes-key.pem \\
  --etcd-servers=https://${MASTER_0_INTERNAL_IP}:2379,https://${MASTER_1_INTERNAL_IP}:2379,https://${MASTER_2_INTERNAL_IP}:2379 \\
  --event-ttl=1h \\
  --experimental-encryption-provider-config=/var/lib/kubernetes/encryption-config.yaml \\
  --kubelet-certificate-authority=/var/lib/kubernetes/ca.pem \\
  --kubelet-client-certificate=/var/lib/kubernetes/kubernetes.pem \\
  --kubelet-client-key=/var/lib/kubernetes/kubernetes-key.pem \\
  --kubelet-https=true \\
  --runtime-config=api/all \\
  --service-account-key-file=/var/lib/kubernetes/service-account.pem \\
  --service-cluster-ip-range=10.32.0.0/24 \\
  --service-node-port-range=30000-32767 \\
  --tls-cert-file=/var/lib/kubernetes/kubernetes.pem \\
  --tls-private-key-file=/var/lib/kubernetes/kubernetes-key.pem \\
  --v=2
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF
  • (masters) kube-controller-manager用のkubeconfigの配置とsystemd unitファイルを作成します。--cluster-cidrオプションにはPodが利用するIP帯172.17.0.0/16を指定しておきます。
sudo mv kube-controller-manager.kubeconfig /var/lib/kubernetes/
cat <<EOF | sudo tee /etc/systemd/system/kube-controller-manager.service
[Unit]
Description=Kubernetes Controller Manager
Documentation=https://github.com/kubernetes/kubernetes

[Service]
ExecStart=/usr/local/bin/kube-controller-manager \\
  --address=0.0.0.0 \\
  --cluster-cidr=172.17.0.0/16 \\
  --cluster-name=kubernetes \\
  --cluster-signing-cert-file=/var/lib/kubernetes/ca.pem \\
  --cluster-signing-key-file=/var/lib/kubernetes/ca-key.pem \\
  --kubeconfig=/var/lib/kubernetes/kube-controller-manager.kubeconfig \\
  --leader-elect=true \\
  --root-ca-file=/var/lib/kubernetes/ca.pem \\
  --service-account-private-key-file=/var/lib/kubernetes/service-account-key.pem \\
  --service-cluster-ip-range=10.32.0.0/24 \\
  --use-service-account-credentials=true \\
  --allocate-node-cidrs=true \\
  --v=2
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF
  • (masters) kube-scheduler用の設定ファイルおよびkubeconfigの配置とsystemd unitファイルを作成します。
sudo mv kube-scheduler.kubeconfig /var/lib/kubernetes/
cat <<EOF | sudo tee /etc/kubernetes/config/kube-scheduler.yaml
apiVersion: kubescheduler.config.k8s.io/v1alpha1
kind: KubeSchedulerConfiguration
clientConnection:
  kubeconfig: "/var/lib/kubernetes/kube-scheduler.kubeconfig"
leaderElection:
  leaderElect: true
EOF

cat <<EOF | sudo tee /etc/systemd/system/kube-scheduler.service
[Unit]
Description=Kubernetes Scheduler
Documentation=https://github.com/kubernetes/kubernetes

[Service]
ExecStart=/usr/local/bin/kube-scheduler \\
  --config=/etc/kubernetes/config/kube-scheduler.yaml \\
  --v=2
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF
  • (masters) Masterサーバの各種サービスを有効化します。
sudo systemctl daemon-reload
sudo systemctl enable kube-apiserver kube-controller-manager kube-scheduler
sudo systemctl start kube-apiserver kube-controller-manager kube-scheduler
  • (masters(hardway-master-0のみ)) kube-apiserverがWorkerサーバのkubeletに接続可能にするため、RBACの設定を入れておきます。
cat <<EOF | kubectl apply --kubeconfig admin.kubeconfig -f -
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  annotations:
    rbac.authorization.kubernetes.io/autoupdate: "true"
  labels:
    kubernetes.io/bootstrapping: rbac-defaults
  name: system:kube-apiserver-to-kubelet
rules:
  - apiGroups:
      - ""
    resources:
      - nodes/proxy
      - nodes/stats
      - nodes/log
      - nodes/spec
      - nodes/metrics
    verbs:
      - "*"
EOF

cat <<EOF | kubectl apply --kubeconfig admin.kubeconfig -f -
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: system:kube-apiserver
  namespace: ""
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: system:kube-apiserver-to-kubelet
subjects:
  - apiGroup: rbac.authorization.k8s.io
    kind: User
    name: kubernetes
EOF

Workerサーバの構築

ここではWorkerサーバを構築し、Kubernetesクラスタに参加させます。

  • (workers) OS依存パッケージのインストールを行います。
sudo yum -y install socat conntrack ipset
  • (workers) Workerサーバ用コンポーネントのバイナリをダウンロードし、適切な場所に配置します。
sudo yum -y install wget
wget \
  https://github.com/kubernetes-sigs/cri-tools/releases/download/v1.13.0/crictl-v1.13.0-linux-amd64.tar.gz \
    https://github.com/containernetworking/plugins/releases/download/v0.6.0/cni-plugins-amd64-v0.6.0.tgz \
  https://github.com/opencontainers/runc/releases/download/v1.0.0-rc5/runc.amd64 \
  https://github.com/containerd/containerd/releases/download/v1.2.0-rc.0/containerd-1.2.0-rc.0.linux-amd64.tar.gz \
  https://storage.googleapis.com/kubernetes-release/release/v1.13.1/bin/linux/amd64/kubectl \
  https://storage.googleapis.com/kubernetes-release/release/v1.13.1/bin/linux/amd64/kube-proxy \
  https://storage.googleapis.com/kubernetes-release/release/v1.13.1/bin/linux/amd64/kubelet

sudo mkdir -p \
  /var/lib/kubelet \
  /var/lib/kube-proxy \
  /var/lib/kubernetes \
  /var/run/kubernetes \
  /etc/cni/net.d \
  /opt/cni/bin

sudo mv runc.amd64 runc
chmod +x kubectl kube-proxy kubelet runc
sudo mv kubectl kube-proxy kubelet runc /usr/local/bin/
sudo tar -xvf crictl-v1.13.0-linux-amd64.tar.gz -C /usr/local/bin/
sudo tar -xvf cni-plugins-amd64-v0.6.0.tgz -C /opt/cni/bin/
sudo tar -xvf containerd-1.2.0-rc.0.linux-amd64.tar.gz
sudo mv bin/* /bin
sudo rmdir bin
  • (workers) CNI(Container Network Interface)のloopbackインタフェースに対する設定を実施します。
cat <<EOF | sudo tee /etc/cni/net.d/99-loopback.conf
{
    "cniVersion": "0.3.1",
    "type": "loopback"
}
EOF
  • (workers) コンテナランタイム containerdの設定ファイルとsystemd unitファイルを配置します。本家Kubernetes the Hard WayではgVisorにも対応した設定を行っていますが、CentOS 7のカーネルがデフォルトでは古く、gVisorに対応していないため、省いています。
sudo mkdir -p /etc/containerd/
cat << EOF | sudo tee /etc/containerd/config.toml
[plugins]
  [plugins.cri.containerd]
    snapshotter = "overlayfs"
    [plugins.cri.containerd.default_runtime]
      runtime_type = "io.containerd.runtime.v1.linux"
      runtime_engine = "/usr/local/bin/runc"
      runtime_root = ""
EOF

cat <<EOF | sudo tee /etc/systemd/system/containerd.service
[Unit]
Description=containerd container runtime
Documentation=https://containerd.io
After=network.target

[Service]
ExecStartPre=/sbin/modprobe overlay
ExecStart=/bin/containerd
Restart=always
RestartSec=5
Delegate=yes
KillMode=process
OOMScoreAdjust=-999
LimitNOFILE=1048576
LimitNPROC=infinity
LimitCORE=infinity

[Install]
WantedBy=multi-user.target
EOF
  • (workers) kubelet用の証明書群およびkubeconfigを配置し、設定ファイルとsystemd unitファイルを作成します。
WORKER_HOSTNAME=$(hostname -s)
sudo mv ${WORKER_HOSTNAME}-key.pem ${WORKER_HOSTNAME}.pem /var/lib/kubelet/
sudo mv ${WORKER_HOSTNAME}.kubeconfig /var/lib/kubelet/kubeconfig
sudo mv ca.pem /var/lib/kubernetes/

cat <<EOF | sudo tee /var/lib/kubelet/kubelet-config.yaml
kind: KubeletConfiguration
apiVersion: kubelet.config.k8s.io/v1beta1
authentication:
  anonymous:
    enabled: false
  webhook:
    enabled: true
  x509:
    clientCAFile: "/var/lib/kubernetes/ca.pem"
authorization:
  mode: Webhook
clusterDomain: "cluster.local"
clusterDNS:
  - "10.32.0.10"
resolvConf: "/etc/resolv.conf"
runtimeRequestTimeout: "15m"
tlsCertFile: "/var/lib/kubelet/${WORKER_HOSTNAME}.pem"
tlsPrivateKeyFile: "/var/lib/kubelet/${WORKER_HOSTNAME}-key.pem"
EOF

cat <<EOF | sudo tee /etc/systemd/system/kubelet.service
[Unit]
Description=Kubernetes Kubelet
Documentation=https://github.com/kubernetes/kubernetes
After=containerd.service
Requires=containerd.service

[Service]
ExecStart=/usr/local/bin/kubelet \\
  --config=/var/lib/kubelet/kubelet-config.yaml \\
  --container-runtime=remote \\
  --container-runtime-endpoint=unix:///var/run/containerd/containerd.sock \\
  --image-pull-progress-deadline=2m \\
  --kubeconfig=/var/lib/kubelet/kubeconfig \\
  --network-plugin=cni \\
  --register-node=true \\
  --hostname-override=${WORKER_HOSTNAME}
  --v=2
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF
  • (workers) kube-proxy用のkubeconfigを配置し、設定ファイルとsystemd unitファイルを作成します。
sudo mv kube-proxy.kubeconfig /var/lib/kube-proxy/kubeconfig

cat <<EOF | sudo tee /var/lib/kube-proxy/kube-proxy-config.yaml
kind: KubeProxyConfiguration
apiVersion: kubeproxy.config.k8s.io/v1alpha1
clientConnection:
  kubeconfig: "/var/lib/kube-proxy/kubeconfig"
mode: "iptables"
clusterCIDR: "172.17.0.0/16"
EOF

cat <<EOF | sudo tee /etc/systemd/system/kube-proxy.service
[Unit]
Description=Kubernetes Kube Proxy
Documentation=https://github.com/kubernetes/kubernetes

[Service]
ExecStart=/usr/local/bin/kube-proxy \\
  --config=/var/lib/kube-proxy/kube-proxy-config.yaml
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF
  • (workers) Workerサーバのサービスを有効化します。同時にKubernetesクラスタへWorkerサーバ登録が行われます。
sudo systemctl daemon-reload
sudo systemctl enable containerd kubelet kube-proxy
sudo systemctl start containerd kubelet kube-proxy

CNI flannelとサービスディスカバリ corednsの導入

ここでは、作業用端末のkubectlを作成したKubernetesクラスタに接続できるよう設定し、CNIとサービスディスカバリを導入して最低限、Kubernetesが機能するようにします。

kubectl config set-cluster kubernetes-the-hard-way \
  --certificate-authority=ca.pem \
  --embed-certs=true \
  --server=https://kubernetes-the-hard-way.bbrfkr.mydns.jp:6443

kubectl config set-credentials admin \
  --client-certificate=admin.pem \
  --client-key=admin-key.pem

kubectl config set-context kubernetes-the-hard-way \
  --cluster=kubernetes-the-hard-way \
  --user=admin

kubectl config use-context kubernetes-the-hard-way
  • (bastion) CNI flannelをデプロイします。
wget https://raw.githubusercontent.com/coreos/flannel/bc79dd1505b0c8681ece4de4c0d86c5cd2643275/Documentation/kube-flannel.yml
sed -i 's@10.244.0.0/16@172.17.0.0/16@g' kube-flannel.yml
kubectl apply -f kube-flannel.yml
  • (bastion) サービスディスカバリ corednsをデプロイします。
kubectl apply -f https://storage.googleapis.com/kubernetes-the-hard-way/coredns.yaml

virtual-kubeletをAWS Fargateで試してみた

現在開催中のJapan Container Days 1812の基調講演でvirtual-kubeletなるプロダクトを聞いたので、サクッと試してみました。

virtual-kubeletとは

Kubernetesにおけるkubeletの実装の一つで、podの作成先を他のプラットフォームのAPIに依頼することができるとのこと。これによって、AzureのACIやAWSのFargateなどのサーバレスフレームワークKubernetesと連携、Kubernetes側で操作できるようになります。

今回の前提条件

以下の環境を前提に解説します。

Get Started!

まず、virtual-kubeletを動作させるVMにGoとawscliを導入します。この導入手順は割愛します。

次にvirtual-kubeletのバイナリをビルドします。

mkdir -p $GOPATH/src/github.com/virtual-kubelet
cd $GOPATH/src/github.com/virtual-kubelet
git clone https://github.com/virtual-kubelet/virtual-kubelet
cd virtual-kubelet
make build

ビルドしたら、バイナリをパスの通るところに置きます。

mv bin/virtual-kubelet /usr/bin/

続いてvirtual-kubeletのprovider設定ファイルを作ります。

mkdir -p /etc/kubernetes/
cp providers/aws/fargate.toml /etc/kubernetes/
cat <<EOF > /etc/kubernetes/fargate.toml
Region = "<Fargateクラスタのあるリージョン>"
ClusterName = "<Fargateクラスタの名前>"
Subnets = [<Fargateクラスタの所属するサブネットのカンマ区切りのリスト>]
SecurityGroups = ["<タスクにアタッチしたいSG>"]
AssignPublicIPv4Address = true
ExecutionRoleArn = ""
CloudWatchLogGroupName = ""
PlatformVersion = "LATEST"
OperatingSystem = "Linux"
CPU = "20"
Memory = "40Gi"
Pods = "20"
EOF

virtual-kubeletがKubernetesのマスターに接続するためのkubeconfigをここで作成します。

cat <<EOF > /etc/kubernetes/virtual-kubelet.conf
<kubeconfigの中身>
EOF

最後にsystemdのunitファイルと環境変数ファイルを作ります。

cat <<EOF > /etc/sysconfig/virtual-kubelet
# provider name (required)
PROVIDER_NAME="aws"
# provider config file path (required)
PROVIDER_CONFIG="/etc/kubernetes/fargate.toml"
# virtual-kubelet port (required)
KUBELET_PORT=10250
# other options
OPTIONS="--kubeconfig=/etc/kubernetes/virtual-kubelet.conf"
EOF
cat <<EOF > /usr/lib/systemd/system/virtual-kubelet.service
[Unit]
Description=Virtual Kubelet

[Service]
User=root
EnvironmentFile=-/etc/sysconfig/virtual-kubelet
ExecStart=/usr/bin/virtual-kubelet --provider \$PROVIDER_NAME --provider-config \$PROVIDER_CONFIG \$OPTIONS

[Install]
WantedBy=multi-user.target
EOF

最後にvirtual-kubeletサービスを有効化します。

systemctl daemon-reload
systemctl enable virtual-kubelet
systemctl start virtual-kubelet

以上で、Kubernetesにvirtual-kubeletというノード名のノードが追加されているはずです。

kubectl get node
NAME                             STATUS                     ROLES    AGE   VERSION
test-k8s-zpo7woffkomq-master-0   Ready,SchedulingDisabled   <none>   11h   v1.11.1
test-k8s-zpo7woffkomq-minion-0   Ready                      <none>   11h   v1.11.1
virtual-kubelet                  Ready                      agent    9h    v1.11.2

試しにvirtual-kubeletでPodが起動するようなDeploymentを作ってみます。

cat <<EOF | kubectl apply -f -
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  labels:
    app: httpd
  name: httpd
  namespace: default
spec:
  progressDeadlineSeconds: 600
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: httpd
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: httpd
    spec:
      containers:
      - image: httpd
        imagePullPolicy: Always
        name: httpd
        resources: {}
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 30
      nodeSelector:
        kubernetes.io/role: agent
        beta.kubernetes.io/os: linux
        type: virtual-kubelet
      tolerations:
      - key: virtual-kubelet.io/provider
        operator: Exists
      - key: azure.com/aci
        effect: NoSchedule
EOF

しばらくするとPodがRunningになります。

kubectl get pod
NAME                     READY   STATUS    RESTARTS   AGE
httpd-7b5896b895-45zh6   1/1     Running   0          44s

Fargate側を確認すると、タスクが起動していることがわかります!

f:id:bbrfkr:20181205103801p:plain

Neutron LBaaSのオートヒーリング設定

少しずつ、プライベートクラウド(OpenStack)の構築時ナレッジを書いていこうと思います。本日はNeutron LBaaSについてです。

Neutron LBaaS自身、Octaviaにその役割を譲りつつあり、Deprecateなのですが、LB用VM立てるよりもHAproxyのプロセスをネットワークノードに立てるほうがデプロイが高速なので、いまだに使っています。可用性を気にしなくていい環境では手軽にLBをデプロイできるので、なくなってほしくないですね。。。

Neutron LBaaSですが、デフォルトではLB(HAproxy)が起動しているネットワークノードが死ぬと、当然ながらLBも死んでしまいます。しかし、ネットワークノードが冗長化されている状況では、別ノードでLBを再起動してほしいわけです。そんなことを実現してくれるneutron.confのパラメータが用意されています。

allow_automatic_lbaas_agent_failover = true

このパラメータを設定し、LBaaS Agentを冗長化するだけで、LBのオートヒーリングが実現できます!

docker-composeでNginx + Django + MySQLのWeb三階層を構成する

Kubernetesクラスタの構築方法など、インフラ観点での記事を書くことが多い本ブログですが、Kubernetesを学んでみると

  • インフラエンジニアもアプリケーションエンジニアの気持ちがわかったほうがいい!

というお気持ちになるのです。CI/CDとか考えると特に。

そんなことでOSにも標準でインストールされている意味でメジャーな言語であるPythonを、そのWebフレームワークであるDjangoを通して勉強しているわけなのですが、DjangoSQLiteとか、python manage.py runserverとか使っていると、「本番環境だとこれどう変わるのん?」というインフラエンジニアならではの疑問が沸々と湧き上がってくるのです。

そこで、Pythonのお勉強のわき道にそれて、Nginx + Django + MySQLでWeb三階層を組んでみました。せっかくDockerコンテナのことも知っているので、docker-composeを使い、コンテナでWeb三階層を組んでみました。そのときのdocker-compose.ymlをさらしたいと思います。

いきなりdocker-compose.ymlをさらす前に、プロジェクトのディレクトリ構成を示しておきます。

.
├── docker-compose.yml        
├── mysite                   ← Djangoプロジェクトのルート
│   ├── Dockerfile             チュートリアルアプリであるpollsアプリを作成
│   ├── docker-entrypoint.sh
│   └── mysite
│       ├── manage.py
│       ├── mysite
│       ├── polls
│       └── static
├── mysql                    ← MySQLプロジェクトのルート
│   └── Dockerfile             mysql:5.7イメージを使うだけ
└── nginx                    ← Nginxプロジェクトのルート
    ├── Dockerfile             基本的にはnginx:1.15.alpineを使うだけだが、
    ├── docker-entrypoint.sh   Djangoと連携するためのUWSGI接続設定を組み込む
    └── mysite_nginx.conf

この前提の下で、docker-compose.ymlは以下のように書きました。

version: '3'

services:
  mysite:
    build: mysite
    image: bbrfkr0129/mysite
    environment:
      MYSQL_DATABASE: mysite
      MYSQL_USER: mysite
      MYSQL_PASSWORD: password
      MYSQL_HOSTNAME: mysql
    networks:
    - mysite
  mysql:
    build: mysql
    image: bbrfkr0129/mysql
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: mysite
      MYSQL_USER: mysite
      MYSQL_PASSWORD: password
    volumes:
    - "mysql_data:/var/lib/mysql"
    networks:
    - mysite
  nginx:
    build: nginx
    image: bbrfkr0129/nginx
    ports:
    - "80:80"
    environment:
      UWSGI_HOST: mysite
    volumes:
    - "./mysite/mysite/static:/static"
    networks:
    - mysite

volumes:
  mysql_data:
    driver: local

networks:
  mysite:
    driver: bridge

mysqlサービスは、公式の使い方に準拠しているので、説明を省きます。 mysiteサービスではDjangoをuWSGI接続で動作させます。バックエンドDBにMySQLを使うので、あらかじめsettings.pyMySQL接続用のパラメータを置換用文字列で書き換えておき、コンテナ起動時にdocker-entrypoint.shの処理内で実際のパラメータに置換します。Dockerfiledocker-entrypoint.shは以下のような感じです。

FROM python:3.6-alpine3.8

# install django uwsgi and PyMySQL
RUN apk --no-cache --virtual .requirements add g++ make linux-headers && \
    apk --no-cache add libffi-dev mysql-dev mysql-client && \
    pip install Django uwsgi mysqlclient && rm -rf ~/.cache/pip && \
    apk del .requirements

# install mysite
RUN mkdir /mysite
COPY ./mysite/ /mysite/
WORKDIR /mysite

EXPOSE 8001
COPY docker-entrypoint.sh /usr/bin/
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["uwsgi", "--socket", ":8001", "--module", "mysite.wsgi"]
#!/bin/sh
sed -i "s/MYSQL_DATABASE/$MYSQL_DATABASE/g" /mysite/mysite/settings.py
sed -i "s/MYSQL_USER/$MYSQL_USER/g" /mysite/mysite/settings.py
sed -i "s/MYSQL_PASSWORD/$MYSQL_PASSWORD/g" /mysite/mysite/settings.py
sed -i "s/MYSQL_HOSTNAME/$MYSQL_HOSTNAME/g" /mysite/mysite/settings.py

export connected="no"
while [ $connected = "no" ]
do
  mysql \
    -u$MYSQL_USER -p$MYSQL_PASSWORD -h$MYSQL_HOSTNAME \
    -e "show tables;" $MYSQL_DATABASE
  if [ $? -eq 0 ] ; then
    export connected="yes"
  fi 
  sleep 1
done 

python manage.py makemigrations polls
python manage.py migrate 
python manage.py shell -c "\
from django.contrib.auth import get_user_model;\
User = get_user_model();\
User.objects.create_superuser('admin', 'admin@example.com', 'admin')\
"

exec "$@"

nginxサービスでは基本的にnginx:1.15-alpineイメージを使いますが、DjangoのuWSGIに接続するための情報が必要なので、このための設定ファイルをdocker buildで埋め込み、コンテナ起動時にdocker-entrypoint.shの処理内でパラメータに置換します。Dockerfilemysite_nginx.confdocker-entrypoint.shは以下のような感じです。

# the upstream component nginx needs to connect to
upstream django {
    # server unix:///mysite/mysite.sock; # for a file socket
    server UWSGI_HOST:8001; # for a web port socket (we'll use this first)
}

# configuration of the server
server {
    # the port your site will be served on
    listen      80;
    # the domain name it will serve for
    server_name example.com; # substitute your machine's IP address or FQDN
    charset     utf-8;

    # max upload size
    client_max_body_size 75M;   # adjust to taste

    location /static {    
        alias /static; 
    }

    # Finally, send all non-media requests to the Django server.
    location / {
        uwsgi_pass  django;
        include     /etc/nginx/uwsgi_params; # the uwsgi_params file you installed
    }
}
FROM nginx:1.15-alpine

RUN rm -f /etc/nginx/conf.d/*
COPY mysite_nginx.conf /etc/nginx/conf.d/mysite_nginx.conf
COPY docker-entrypoint.sh /usr/bin/
RUN chmod 755 /usr/bin/docker-entrypoint.sh
RUN mkdir /static
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["nginx", "-g", "daemon off;"]
#!/bin/sh
sed -i "s/UWSGI_HOST/$UWSGI_HOST/g" /etc/nginx/conf.d/mysite_nginx.conf
exec "$@"

Webサービスを起動するときは以下のコマンドを叩けばOKです。上がったらhttp://localhost/admin|Djangoの管理者サイトにアクセスできるはずです。

docker-compose up -d

すべてきれいさっぱり消すときは...(永続データも消えるのでちゅうい)

docker-compose down -v

Docker for WindowsでMySQLコンテナのデータを永続化する

Docker for WindowsMySQLコンテナのデータを永続化する際、ホストディレクトリを/var/lib/mysqlに単純にマウントすると、MySQLコンテナ側で以下のようなエラーが出ます。

2018-11-18T10:02:32.761277Z 1 [ERROR] [MY-012646] [InnoDB] File ./ibdata1: 'open' returned OS error 71. Cannot
continue operation

このエラーを回避するにはDockerボリュームを使ってデータを永続化すると、うまくいくみたいです。具体的にはdocker-compose.ymlに以下のように記載して起動するとうまくいきます。

version: '3'

services:
  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: mydb
      MYSQL_USER: myuser
      MYSQL_PASSWORD: password
    volumes:
    - "mysql_data:/var/lib/mysql"

volumes:
  mysql_data:
    driver: local

なぜかまでは調査できてませんが、Windows側のディレクトパーミッションとか関係あるのかしら。。。