ビビリフクロウの足跡

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

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側のディレクトパーミッションとか関係あるのかしら。。。

既定のアプリをコマンドで変更する方法

Windows 10の10月24日のアップデートでGUI上から一部の既定のアプリを変更することができないようになっているみたいです。これに見事にはまり、.txtファイルの既定のアプリをメモ帳から変更できなくなってしまいました。。。

Microsoft側もこのことを認識しており、11月下旬に向けて修正を予定しているようです。 https://support.microsoft.com/en-us/help/4462933/windows-10-update-kb4462933

Symptom Workaround
After installing this update, some users cannot set Win32 program defaults for certain app and file type combinations using the Open with… command or Settings > Apps > Default apps. In some cases, Microsoft Notepad or other Win32 programs cannot be set as the default. In some cases, attempting to set application defaults again will succeed. Microsoft is working on a resolution and estimates a solution will be available in late November 2018.

11月下旬まで待ってられるか!という人は以下のようにして変更できるみたいです。

ftype txtfile="<テキストエディタのパス>" "%1"

OpenStackとoVirtで自宅クラウド環境を構築してみた

f:id:bbrfkr:20181104113638p:plain

お久しぶりです。4か月間ぐらい全くポストしていませんでしたが生きています。

何やっていたかというと、自宅環境をOpenStackによってプライベートクラウド化しておりました。これが完成しないと諸々の検証ができないので、ブログのポストより優先していたら4か月も経っていた感じです。

4か月間にためていたナレッジは少しずつ文章にしていくつもりですが、まずはどんなプライベートクラウド環境を構築していたのか紹介する記事を書こうと思います。自宅環境を構築したい人、している人の少しでも参考になれば幸いです。

目的

プライベートクラウド環境の目的ですが、以下の通りです。

  • 製品の検証(主にインフラの検証。ハイパバイザレベルでの検証も含む)
  • サービス提供(Wikiサイトなど)

従来はoVirtというOSSの仮想化基盤製品でこれらの目的をこなしていたわけですが、プライベートクラウド化することによって次のことを狙っていました。

  • 様々なサービスのAPIコールによるデプロイ

oVirtはあくまで仮想化基盤なので、あくまでもAmazon EC2やEBS相当の機能しか提供してくれません。昨今のクラウドネイティブなアーキテクチャの学習には不向きだったのです。またコンテナ中核技術であるKubernetesの利便性を最大限引き出すにはクラウドとの連携は必須となります。このためOpenStackによって自宅IaaSを構築しようという考えに至りました。

ただし、OpenStackでは柔軟な仮想マシン設定を行うことが難しくなり、KVMなどのハイパバイザレベルでの動作検証が難しくなります。したがって検証環境すべてをOpenStackに移行したわけではなく、一部リソースをoVirt用に残すようにしました。

アーキテクチャ(物理)

物理構成は以下の通りになります。OpenStack Controllerはロードバランサなどの補助サーバはoVirt上のVMとして構築します。

f:id:bbrfkr:20181104114922p:plain

  • Intel NUC Pentiumモデル x 1 (oVirt Engine用)
  • Intel NUC Corei3モデル x 3 (oVirt Node用)
    • Mem: 32GB/1マシン
    • SSD: 500GB M.2
    • HDD: 1TB
    • NIC: 1GbE x 4
  • Intel NUC Corei3モデル x 3 (OpenStack Compute用)
    • Mem: 32GB/1マシン
    • SSD: 500GB M.2
    • HDD: 1TB
    • NIC: 1GbE x 4

アーキテクチャ(仮想)

VMを含めた構成は以下の通りです。冗長化による性能向上を狙って、OpenStack環境はActive/Activeクラスタを構成しています。(Cinder Volumeを除く)

f:id:bbrfkr:20181104121209p:plain

構築したサービス

プライベートクラウドでは以下のIaaSサービスを提供しています。

  • oVirt
    通常の仮想化環境を提供します。KVM等のハイパバイザ自体を検証したいときはこっちを使います。VMやボリュームのプロビジョニングを行うことができ、通常の仮想化環境といってもAPIがついているので、Ansible等を用いてプロビジョニングを自動化することが可能です。

  • OpenStack Keystone
    AWSにおけるIAM相当の認証基盤を提供します。

  • OpenStack Swift
    AWSにおけるS3相当のオブジェクトストレージ機能を提供します。

  • OpenStack Glance
    AWSにおけるAMI相当のVMイメージストア機能を提供します。Swiftと連携してイメージをSwiftに保存するようにしています。

  • OpenStack Nova
    AWSにおけるEC2相当のVM機能を提供します。

  • OpenStack Neutron
    AWSにおけるVPC、ELB相当の仮想ネットワーク、ソフトウェアロードバランサー機能を提供します。

  • OpenStack Horizon
    AWSにおけるマネジメントコンソール相当のダッシュボード機能を提供します。

  • OpenStack Cinder
    AWSにおけるEBS相当のブロックストレージ機能を提供します。

  • OpenStack Heat
    AWSにおけるCloudFormation相当のオーケストレーション機能を提供します。

  • OpenStack Trove
    AWSにおけるRDS相当のRDBMS機能を提供します。

  • OpenStack Manila
    AWSにおけるEFS相当の共有ファイルシステム機能を提供します。

  • OpenStack Designate
    AWSにおけるRoute53相当のDNS機能を提供します。

  • OpenStack Magnum
    AWSにおけるECS、EKS相当のコンテナオーケストレーション機能を提供します。Kubernetesのデプロイを一発で行えます。

まとめ

OSSを使って自宅環境をプライベートクラウド化してみましたが、ソフトウェアでインフラを制御するのはとても便利で快感です! お時間ある方は是非チャレンジしてみてはいかがでしょうか? 設計や構築方法について疑問がある方は質問していただければ可能な限りお答えしたいと思います!

kubeadmを用いたKubernetes HAクラスタ on AWS(v.1.11対応版)

前の記事を公開してすぐにv.1.11が出てしまい、構築方法が変わったので、泣きながら再検証...

構成

  • OS: CentOS7
  • サーバ群
    • Master + etcd
      • 3台(aws-k8s-master[01-03])
    • Node
      • 3台(aws-k8s-node[01-03])

前提

  • 作業ユーザはroot

手順

NLBの作成(AWS ManagementConsole等)

(6443/TCPのリスナを作成、aws-k8s-master[01-03]のIPをターゲットグループに登録する)

Dockerのインストール(aws-k8s-master[01-03]、aws-k8s-node[01-03])

yum install -y docker
systemctl enable docker && systemctl start docker

kubeadm、kubelet、kubectlのインストール(aws-k8s-master[01-03]、aws-k8s-node[01-03])

cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
EOF
sed -i 's/^SELINUX=.*/SELINUX=permissive/g' /etc/selinux/config
setenforce 0
yum install -y kubelet kubeadm kubectl
systemctl enable kubelet && systemctl start kubelet
cat <<EOF >  /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
sysctl --system

最初のMasterサーバの構築(aws-k8s-master01)

cat <<EOF > kubeadm-config.yaml
apiVersion: kubeadm.k8s.io/v1alpha2
kind: MasterConfiguration
kubernetesVersion: v1.11.0
apiServerCertSANs:
- "<NLBのDNS名>"
api:
  controlPlaneEndpoint: "<NLBのDNS名>:6443"
etcd:
  local:
    extraArgs:
      listen-client-urls: "https://127.0.0.1:2379,https://<aws-k8s-master01のIP>:2379"
      advertise-client-urls: "https://<aws-k8s-master01のIP>:2379"
      listen-peer-urls: "https://<aws-k8s-master01のIP>:2380"
      initial-advertise-peer-urls: "https://<aws-k8s-master01のIP>:2380"
      initial-cluster: "aws-k8s-master01=https://<aws-k8s-master01のIP>:2380"
    serverCertSANs:
      - aws-k8s-master01
      - <aws-k8s-master01のIP>
    peerCertSANs:
      - aws-k8s-master01
      - <aws-k8s-master01のIP>
networking:
  podSubnet: "192.168.0.0/16"
EOF
kubeadm init --config kubeadm-config.yaml
mkdir -p $HOME/.kube
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/config

SSHキーペアの作成(aws-k8s-master02, aws-k8s-master03)

ssh-keygen -t rsa -b 4096 -C ""
cat ~/.ssh/id_rsa.pub
# 出力された内容をaws-k8s-master01の/root/.ssh/authorized_keysのエントリを追加する

証明書群、設定ファイル群の配布(aws-k8s-master02、aws-k8s-master03)

mkdir -p /etc/kubernetes/pki/etcd
scp root@<aws-k8s-master01のIP>:/etc/kubernetes/pki/ca.crt /etc/kubernetes/pki/ca.crt
scp root@<aws-k8s-master01のIP>:/etc/kubernetes/pki/ca.key /etc/kubernetes/pki/ca.key
scp root@<aws-k8s-master01のIP>:/etc/kubernetes/pki/sa.key /etc/kubernetes/pki/sa.key
scp root@<aws-k8s-master01のIP>:/etc/kubernetes/pki/sa.pub /etc/kubernetes/pki/sa.pub
scp root@<aws-k8s-master01のIP>:/etc/kubernetes/pki/front-proxy-ca.crt /etc/kubernetes/pki/front-proxy-ca.crt
scp root@<aws-k8s-master01のIP>:/etc/kubernetes/pki/front-proxy-ca.key /etc/kubernetes/pki/front-proxy-ca.key
scp root@<aws-k8s-master01のIP>:/etc/kubernetes/pki/etcd/ca.crt /etc/kubernetes/pki/etcd/ca.crt
scp root@<aws-k8s-master01のIP>:/etc/kubernetes/pki/etcd/ca.key /etc/kubernetes/pki/etcd/ca.key
scp root@<aws-k8s-master01のIP>:/etc/kubernetes/admin.conf /etc/kubernetes/admin.conf

2台目のMasterサーバの構築(aws-k8s-master02)

cat <<EOF > kubeadm-config.yaml
apiVersion: kubeadm.k8s.io/v1alpha2
kind: MasterConfiguration
kubernetesVersion: v1.11.0
apiServerCertSANs:
- "<NLBのDNS名>"
api:
  controlPlaneEndpoint: "<NLBのDNS名>:6443"
etcd:
  local:
    extraArgs:
      listen-client-urls: "https://127.0.0.1:2379,https://<aws-k8s-master02のIP>:2379"
      advertise-client-urls: "https://<aws-k8s-master02のIP>:2379"
      listen-peer-urls: "https://<aws-k8s-master02のIP>:2380"
      initial-advertise-peer-urls: "https://<aws-k8s-master02のIP>:2380"
      initial-cluster: "aws-k8s-master01=https://<aws-k8s-master01のIP>:2380,aws-k8s-master02=https://<aws-k8s-master02のIP>:2380"
      initial-cluster-state: existing
    serverCertSANs:
      - aws-k8s-master02
      - <aws-k8s-master02のIP>
    peerCertSANs:
      - aws-k8s-master02
      - <aws-k8s-master02のIP>
networking:
  podSubnet: "192.168.0.0/16"
EOF
kubeadm alpha phase certs all --config kubeadm-config.yaml
kubeadm alpha phase kubelet config write-to-disk --config kubeadm-config.yaml
kubeadm alpha phase kubelet write-env-file --config kubeadm-config.yaml
kubeadm alpha phase kubeconfig kubelet --config kubeadm-config.yaml
systemctl start kubelet
CP0_IP=<aws-k8s-master01のIP>
CP0_HOSTNAME=aws-k8s-master01
CP1_IP=<aws-k8s-master02のIP>
CP1_HOSTNAME=aws-k8s-master02

KUBECONFIG=/etc/kubernetes/admin.conf kubectl exec -n kube-system etcd-${CP0_HOSTNAME} -- etcdctl --ca-file /etc/kubernetes/pki/etcd/ca.crt --cert-file /etc/kubernetes/pki/etcd/peer.crt --key-file /etc/kubernetes/pki/etcd/peer.key --endpoints=https://${CP0_IP}:2379 member add ${CP1_HOSTNAME} https://${CP1_IP}:2380
kubeadm alpha phase etcd local --config kubeadm-config.yaml
kubeadm alpha phase kubeconfig all --config kubeadm-config.yaml
kubeadm alpha phase controlplane all --config kubeadm-config.yaml
kubeadm alpha phase mark-master --config kubeadm-config.yaml

3台目のMasterサーバの構築(aws-k8s-master03)

cat <<EOF > kubeadm-config.yaml
apiVersion: kubeadm.k8s.io/v1alpha2
kind: MasterConfiguration
kubernetesVersion: v1.11.0
apiServerCertSANs:
- "<NLBのDNS名>"
api:
  controlPlaneEndpoint: "<NLBのDNS名>:6443"
etcd:
  local:
    extraArgs:
      listen-client-urls: "https://127.0.0.1:2379,https://<aws-k8s-master03のIP>:2379"
      advertise-client-urls: "https://<aws-k8s-master03のIP>:2379"
      listen-peer-urls: "https://<aws-k8s-master03のIP>:2380"
      initial-advertise-peer-urls: "https://<aws-k8s-master03のIP>:2380"
      initial-cluster: "aws-k8s-master01=https://<aws-k8s-master01のIP>:2380,aws-k8s-master02=https://<aws-k8s-master02のIP>:2380,aws-k8s-master03=https://<aws-k8s-master03のIP>:2380"
      initial-cluster-state: existing
    serverCertSANs:
      - aws-k8s-master03
      - <aws-k8s-master03のIP>
    peerCertSANs:
      - aws-k8s-master03
      - <aws-k8s-master03のIP>
networking:
  podSubnet: "192.168.0.0/16"
EOF
kubeadm alpha phase certs all --config kubeadm-config.yaml
kubeadm alpha phase kubelet config write-to-disk --config kubeadm-config.yaml
kubeadm alpha phase kubelet write-env-file --config kubeadm-config.yaml
kubeadm alpha phase kubeconfig kubelet --config kubeadm-config.yaml
systemctl start kubelet
CP0_IP=<aws-k8s-master01のIP>
CP0_HOSTNAME=aws-k8s-master01
CP2_IP=<aws-k8s-master03のIP>
CP2_HOSTNAME=aws-k8s-master03

KUBECONFIG=/etc/kubernetes/admin.conf kubectl exec -n kube-system etcd-${CP0_HOSTNAME} -- etcdctl --ca-file /etc/kubernetes/pki/etcd/ca.crt --cert-file /etc/kubernetes/pki/etcd/peer.crt --key-file /etc/kubernetes/pki/etcd/peer.key --endpoints=https://${CP0_IP}:2379 member add ${CP2_HOSTNAME} https://${CP2_IP}:2380
kubeadm alpha phase etcd local --config kubeadm-config.yaml
kubeadm alpha phase kubeconfig all --config kubeadm-config.yaml
kubeadm alpha phase controlplane all --config kubeadm-config.yaml
kubeadm alpha phase mark-master --config kubeadm-config.yaml

flannelのインストール(aws-k8s-master01)

curl -O https://raw.githubusercontent.com/coreos/flannel/v0.10.0/Documentation/kube-flannel.yml
sed -i 's/10.244.0.0/192.168.0.0/g' kube-flannel.yml
kubectl apply -f kube-flannel.yml

Nodeのクラスタ参加(aws-k8s-node[01-03])

kubeadm join --token <token> <NLBのDNS名>:6443 --discovery-token-ca-cert-hash sha256:<hash>
# <token>、<hash>はkubeadm initをaws-k8s-master01で実行した際に表示されたものを使用

kubeadmを用いたKubernetes HAクラスタ on AWS

AWS上でkubeadmを使い、Kubernetes HAクラスタを作ってみたときのメモです。

構成

  • OS: CentOS7
  • サーバ群
    • Master + etcd
      • 3台(aws-k8s-master[01-03])
    • Node
      • 3台(aws-k8s-node[01-03])

前提

  • 作業ユーザはroot

手順

Dockerのインストール(aws-k8s-master[01-03]、aws-k8s-node[01-03])

yum install -y docker
systemctl enable docker && systemctl start docker

kubeadm、kubelet、kubectlのインストール(aws-k8s-master[01-03]、aws-k8s-node[01-03])

cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
EOF
sed -i 's/^SELINUX=.*/SELINUX=permissive/g' /etc/selinux/config
setenforce 0
yum install -y kubelet kubeadm kubectl
systemctl enable kubelet && systemctl start kubelet
cat <<EOF >  /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
sysctl --system

cfssl、cfssljsonのインストール(aws-k8s-master[01-03])

curl -o /usr/local/bin/cfssl https://pkg.cfssl.org/R1.2/cfssl_linux-amd64
curl -o /usr/local/bin/cfssljson https://pkg.cfssl.org/R1.2/cfssljson_linux-amd64
chmod +x /usr/local/bin/cfssl*

etcdサーバのためのCA証明書作成(aws-k8s-master01)

mkdir -p /etc/etcd/pki
cd /etc/etcd/pki
cat >ca-config.json <<EOF
{
   "signing": {
       "default": {
           "expiry": "175200h"
       },
       "profiles": {
           "server": {
               "expiry": "175200h",
               "usages": [
                   "signing",
                   "key encipherment",
                   "server auth",
                   "client auth"
               ]
           },
           "client": {
               "expiry": "175200h",
               "usages": [
                   "signing",
                   "key encipherment",
                   "client auth"
               ]
           },
           "peer": {
               "expiry": "175200h",
               "usages": [
                   "signing",
                   "key encipherment",
                   "server auth",
                   "client auth"
               ]
           }
       }
   }
}
EOF
# 証明書の有効期限は20年
cat >ca-csr.json <<EOF
{
   "CN": "etcd",
   "key": {
       "algo": "rsa",
       "size": 2048
   }
}
EOF
cfssl gencert -initca ca-csr.json | cfssljson -bare ca -

etcdのクライアント証明書の作成(aws-k8s-master01)

cat >client.json <<EOF
{
  "CN": "client",
  "key": {
      "algo": "ecdsa",
      "size": 256
  }
}
EOF
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=client client.json | cfssljson -bare client

環境変数のエクスポート(aws-k8s-master[01-03])

export PEER_NAME=$(hostname)
export PRIVATE_IP=$(ip addr show eth0 | grep -Po 'inet \K[\d.]+')

SSHキーペアの作成(aws-k8s-master02, aws-k8s-master03)

ssh-keygen -t rsa -b 4096 -C ""
cat ~/.ssh/id_rsa.pub
# 出力された内容をaws-k8s-master01の/root/.ssh/authorized_keysのエントリを追加する

CA証明書とクライアント証明書の配布(aws-k8s-master02, aws-k8s-master03)

mkdir -p /etc/etcd/pki
cd /etc/etcd/pki
scp root@<aws-k8s-master01のIP>:/etc/etcd/pki/ca.pem .
scp root@<aws-k8s-master01のIP>:/etc/etcd/pki/ca-key.pem .
scp root@<aws-k8s-master01のIP>:/etc/etcd/pki/client.pem .
scp root@<aws-k8s-master01のIP>:/etc/etcd/pki/client-key.pem .
scp root@<aws-k8s-master01のIP>:/etc/etcd/pki/ca-config.json .

etcdサーバ証明書とpeer証明書の作成(aws-k8s-master[01-03])

cfssl print-defaults csr > config.json
sed -i '0,/CN/{s/example\.net/'"$PEER_NAME"'/}' config.json
sed -i 's/www\.example\.net/'"$PRIVATE_IP"'/' config.json
sed -i 's/example\.net/'"$PEER_NAME"'/' config.json
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=server config.json | cfssljson -bare server
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=peer config.json | cfssljson -bare peer

etcdサーバのsystemd登録と起動設定(aws-k8s-master[01-03])

export ETCD_VERSION="v3.1.12"
curl -sSL https://github.com/coreos/etcd/releases/download/${ETCD_VERSION}/etcd-${ETCD_VERSION}-linux-amd64.tar.gz | tar -xzv --strip-components=1 -C /usr/local/bin/
touch /etc/etcd.env
echo "PEER_NAME=${PEER_NAME}" >> /etc/etcd.env
echo "PRIVATE_IP=${PRIVATE_IP}" >> /etc/etcd.env
cat >/etc/systemd/system/etcd.service <<EOF
[Unit]
Description=etcd
Documentation=https://github.com/coreos/etcd
Conflicts=etcd.service
Conflicts=etcd2.service

[Service]
EnvironmentFile=/etc/etcd.env
Type=notify
Restart=always
RestartSec=5s
LimitNOFILE=40000
TimeoutStartSec=0

ExecStart=/usr/local/bin/etcd --name <name> --data-dir /var/lib/etcd --listen-client-urls https://<my_ip>:2379 --advertise-client-urls https://<my_ip>:2379 --listen-peer-urls https://<my_ip>:2380 --initial-advertise-peer-urls https://<my_ip>:2380 --cert-file=/etc/etcd/pki/server.pem --key-file=/etc/etcd/pki/server-key.pem --client-cert-auth --trusted-ca-file=/etc/etcd/pki/ca.pem --peer-cert-file=/etc/etcd/pki/peer.pem --peer-key-file=/etc/etcd/pki/peer-key.pem --peer-client-cert-auth --peer-trusted-ca-file=/etc/etcd/pki/ca.pem --initial-cluster aws-k8s-master01=https://<aws-k8s-master01のIP>:2380,aws-k8s-master02=https://<aws-k8s-master02のIP>:2380,aws-k8s-master03=https://<aws-k8s-master03のIP>:2380 --initial-cluster-token my-etcd-token --initial-cluster-state new
# <name>にはaws-k8s-master[01-03]が入る

[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable etcd
systemctl start etcd

NLBの作成(AWS ManagementConsole等)

(6443/TCPのリスナを作成、aws-k8s-master01のインスタンスをターゲットグループに登録する)
(NLB作成後、DNS名にpingを発行し、NLBのIPを確認する)

kubeadmの実行(aws-k8s-master01)

cat >config.yaml <<EOF
apiVersion: kubeadm.k8s.io/v1alpha1
kind: MasterConfiguration
etcd:
  endpoints:
  - https://<aws-k8s-master01のIP>:2379
  - https://<aws-k8s-master02のIP>:2379
  - https://<aws-k8s-master03のIP>:2379
  caFile: /etc/etcd/pki/ca.pem
  certFile: /etc/etcd/pki/client.pem
  keyFile: /etc/etcd/pki/client-key.pem
networking:
  podSubnet: 192.168.0.0/16
apiServerCertSANs:
- <NLBのIP>
- <my_ip>
apiServerExtraArgs:
  apiserver-count: "3"
EOF
kubeadm init --config=config.yaml
mkdir -p $HOME/.kube
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/config

kubeadmの実行(aws-k8s-master02,aws-k8s-master03)

mkdir /etc/kubernetes/pki
scp root@<aws-k8s-master01のIP>:/etc/kubernetes/pki/* /etc/kubernetes/pki
rm -f /etc/kubernetes/pki/apiserver*
cat >config.yaml <<EOF
apiVersion: kubeadm.k8s.io/v1alpha1
kind: MasterConfiguration
etcd:
  endpoints:
  - https://<aws-k8s-master01のIP>:2379
  - https://<aws-k8s-master02のIP>:2379
  - https://<aws-k8s-master03のIP>:2379
  caFile: /etc/etcd/pki/ca.pem
  certFile: /etc/etcd/pki/client.pem
  keyFile: /etc/etcd/pki/client-key.pem
networking:
  podSubnet: 192.168.0.0/16
apiServerCertSANs:
- <NLBのIP>
- <my_ip>
apiServerExtraArgs:
  apiserver-count: "3"
EOF
kubeadm init --config=config.yaml
mkdir -p $HOME/.kube
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/config

NLBへ残りのMasterを追加(AWS ManagementConsole等)

(NLBのターゲットグループにaws-k8s-master02、aws-k8s-master03を追加)

calicoのインストール(aws-k8s-master01)

kubectl apply -f https://docs.projectcalico.org/v3.1/getting-started/kubernetes/installation/hosted/rbac-kdd.yaml
kubectl apply -f https://docs.projectcalico.org/v3.1/getting-started/kubernetes/installation/hosted/kubernetes-datastore/calico-networking/1.7/calico.yaml

Nodeのクラスタ参加(aws-k8s-node[01-03])

kubeadm join --token <token> <aws-k8s-master01のIP>:6443 --discovery-token-ca-cert-hash sha256:<hash>
# <token>、<hash>はkubeadm initをaws-k8s-master01で実行した際に表示されたものを使用

kube-proxyの再設定(aws-k8s-master01)

kubectl get configmap -n kube-system kube-proxy -o yaml > kube-proxy-cm.yaml
sed -i 's#server:.*#server: https://<NLBのIP>:6443#g' kube-proxy-cm.yaml
kubectl apply -f kube-proxy-cm.yaml --force
kubectl delete pod -n kube-system -l k8s-app=kube-proxy

kubeletの再設定(aws-k8s-node[01-03])

sed -i 's#server:.*#server: https://<NLBのIP>:6443#g' /etc/kubernetes/kubelet.conf
systemctl restart kubelet

gVisorを試してみた

Googleが開発したgVisorを試して、簡単に性能調査してみましたので、メモとして残しておきます。

環境

gVisorのインストール手順

  • Dockerのインストール
sudo apt-get update
sudo apt-get install -y \
    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 update
sudo apt-get install -y docker-ce
  • gVisor(runsc)のインストール
wget https://storage.googleapis.com/gvisor/releases/nightly/latest/runsc
chmod +x runsc
sudo mv runsc /usr/local/bin
sudo sh -c 'cat <<EOF > /etc/docker/daemon.json
{
    "runtimes": {
        "runsc": {
            "path": "/usr/local/bin/runsc"
        }
    }
}
EOF'
sudo systemctl restart docker
  • コンテナを起動してみる
sudo docker run --runtime=runc hello-world
sudo docker run --runtime=runsc hello-world

性能調査

簡単なスクリプトでコンテナ起動と削除のスピードを評価します。

cat <<EOF > runc-test.sh
for i in \$(seq 1 \$1) ; do
    sudo docker run --runtime=runc --name gvisor-test hello-world > /dev/null 2>&1
    sudo docker rm gvisor-test > /dev/null 2>&1
done
EOF
chmod +x runc-test.sh

cat <<EOF > runsc-test.sh
for i in \$(seq 1 \$1) ; do
    sudo docker run --runtime=runsc --name gvisor-test hello-world > /dev/null 2>&1
    sudo docker rm gvisor-test > /dev/null 2>&1
done
EOF
chmod +x runsc-test.sh

スクリプトの使い方としては./runc-test.sh <起動および削除回数>./runsc-test.sh <起動および削除回数>とします。試しに両方100回ずつ起動・削除を繰り返すと、以下のような結果となりました。

ubuntu@gvisor-01:~$ time ./runc-test.sh 100

real    1m20.991s
user    0m14.948s
sys     0m1.768s
ubuntu@gvisor-01:~$ time ./runsc-test.sh 100

real    1m13.219s
user    0m15.100s
sys     0m1.748s

起動・停止だけではruncとあまり差はみられませんね。むしろrunscの方が速い? ネットワークトラフィックの差をとるとruncに軍配が上がるみたいですが。。。

speakerdeck.com


追記(2018/06/07) 気になってDisk書き込み速度も調べてみましたが、逆転現象が起きましたw

  • runc
root@4aee2e622884:/# dd if=/dev/zero of=/tmp/write.tmp ibs=1M obs=1M count=1024
1024+0 records in
1024+0 records out
1073741824 bytes (1.1 GB, 1.0 GiB) copied, 4.17698 s, 257 MB/s
  • runsc
root@ce7e3926f52e:/# dd if=/dev/zero of=/tmp/write.tmp ibs=1M obs=1M count=1024
1024+0 records in
1024+0 records out
1073741824 bytes (1.1 GB, 1.0 GiB) copied, 0.853382 s, 1.3 GB/s