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
ユーザを作ります。
レジストリサーバ登録
レジストリサーバ登録をして…
ユーザ作成
一般ユーザbbrfkr
を作ります。
再ログイン
そして再ログイン。
実際にイメージをプッシュしてみる
作ったレジストリサーバにログインするためには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上で確認できるようになります。
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
RUNNIG
がfalse
とあるように、この時点ではまだ仮想マシンは起動していません。起動にはvirtctl
コマンドを使って以下のようにします。
virtctl start testvm
すると、RUNNING
がtrue
に変わります。
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サーバの構築を実施します。
- (masters) Kubernetesの設定ファイル用ディレクトリを作成します。
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が機能するようにします。
- (bastion) kubectlを作成した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側で操作できるようになります。
今回の前提条件
以下の環境を前提に解説します。
- AWS Fargateクラスタ
- 最低でもMasterノードをデプロイしたKubernetesクラスタ
- virtual-kubeletを動作させるためのVM (OS: CentOS7)
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側を確認すると、タスクが起動していることがわかります!
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を通して勉強しているわけなのですが、DjangoでSQLiteとか、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.py
にMySQL接続用のパラメータを置換用文字列で書き換えておき、コンテナ起動時にdocker-entrypoint.sh
の処理内で実際のパラメータに置換します。Dockerfile
、docker-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
の処理内でパラメータに置換します。Dockerfile
、mysite_nginx.conf
、docker-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 WindowsでMySQLコンテナのデータを永続化する際、ホストディレクトリを/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