これは何?
Oracle社が発表したAlways FreeサービスでKubernetesクラスタを作成する方法を紹介します。
Oracle Cloud Free Tier | Oracle 日本
Oracle Cloud Free Tierはクレジットカードの登録さえすれば、無料でいくつかのリソースを利用することができます。
特に今回利用するFree Tierは
- 2つのVM(1/8 CPU, 1GB Memory)
- 10TB/monthの外部通信
です。
以前こういう記事を書きました。
この記事でははVultrというVPSサービスを使い$10でKubernetesクラスタを作る方法を紹介したのですが、そのとき利用したVPSのスペックは 1CPU,1GB Memory のVM2台でした。
CPUこそ劣るものの、これでできるならOracle CloudののFreeTierでもKubernetesクラスタが作れるのでは?と考え挑戦してみました。
注意
この手順を使って作るクラスタはインターネットからアクセスできる状態になります。この手順通り実施したとしても様々な攻撃のリスクが存在しています。この手順を実行し起きたいかなる問題も筆者は責任を負えませんので、個人の責任で作業を行ってください。
また、これらの手順を実行した結果によってOracle Cloudへの課金が生じる可能性があります、こちらに関しても筆者は責任を負いません。
FreeTierへの登録
ここは割愛します。クレジットカードの登録などが必要です。
また、ここで登録したリージョンが「ホームリージョン」となり、Free Tierで利用できるのはそのリージョンだけになるようです(お金を払えばそれとは関係なくどのリージョンでも利用できます。)
ここFreeTierが発表されてしばらくはJapanリージョンではVMが払い出せない状況にありましたが、ここ数日で払い出せるように回復しているように見えます。
追記:この記事を書いている2019/10/06現在、再び払い出せなくなりました。(この症状の時 VM作成時に Out of host capacity というエラーが出ます。)
VMの作成
インスタンスの命名は何でもいいです、イメージソースはここではUbuntuの18.0.4にします。また、パブリックIPを割り当てるために「シェイプ、ネットワーク、ストレージオプションの表示」をクリックします。
そして”Assign in public IP address”を選択します。
さらにSSHキーを適切に設定して「作成」をクリックします。
VMが作成できると「パブリックIPアドレス」が表示されます。このIPにsshすることで、作成したVMにログインできます。
同じ要領でWorkerのVMも作成しておきます。
このようにして作ったVM2台はちょうどAlways Free Tierの要件を満たしており、無料で利用することができます。
これから作るKubernetesクラスタ
master, workerのVMを作り、その2台でKubernetesクラスタを構築します。ingress-controllerをデプロイし、インターネットからそこを経由してクラスタ内のWebアプリケーションにアクセスできるようにします。(0円で)
Kubernetes Nodeの構築
次にKubernetesのNodeとして必要なパッケージのインストールです。これをざざっと実行することで
- 最新のDockerパッケージのインストール
- 最新のKubernetesパッケージのインストール
- (Kubernetesとしては非推奨ですが)Swapの有効化とKubeletの設定
が行えます。
apt-get update && apt-get install -y apt-transport-https ca-certificates curl software-properties-common
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
cat <<EOF >/etc/apt/sources.list.d/kubernetes.list
deb http://apt.kubernetes.io/ kubernetes-xenial main
EOF
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
sudo add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) \
stable"
apt-get update
apt-get install -y docker-ce
apt-get install -y kubelet kubeadm kubectl
dd if=/dev/zero of=/swapfile count=2048 bs=1M
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo "/swapfile none swap sw 0 0" >> /etc/fstab
sed -i 's/\/usr\/bin\/kubelet/\/usr\/bin\/kubelet --fail-swap-on=false --system-reserved=memory=100Mi /' /etc/systemd/system/kubelet.service.d/10-kubeadm.conf
systemctl daemon-reload
systemctl restart kubelet
ネットワークの設定(Oracle Cloud)
Kubernetesクラスタを構築する前にネットワークの設定をする必要があります。
必要なポートの設定はInstalling kubeadm - Kubernetesが参考になります。
要するにMasterはWorkerからの6443、MasterからすべてのNodeへの10250が疎通できれば良さそうです。
またこれから利用するオーバーレイネットワークのWeaveNetで利用する6783ポートも開ける必要があります。
「仮想クラウドネットワーク」から(おそらくmasterという名前(初めに作ったVMと同じ名前になる?)目的のネットワークを選択し、さらにその中にあるサブネットを選択します(おそらく1つしかないです)。さらに、その中のセキュリティリストを選択し、下記のように設定します。
上のほうのルールはもとからあるものです。
ネットワークの設定(iptables)
Oracle Cloudの設定とは別に各ホストのiptablesも設定する必要があります。
master
iptablesの設定は既存の設定に依存します。ここまでセットアップしたVMでは例えば下記のようになっているはずです。
# iptables -L INPUT --line-numbers
Chain INPUT (policy ACCEPT)
num target prot opt source destination
1 ACCEPT all -- anywhere anywhere state RELATED,ESTABLISHED
2 ACCEPT icmp -- anywhere anywhere
3 ACCEPT all -- anywhere anywhere
4 ACCEPT udp -- anywhere anywhere udp spt:ntp
5 ACCEPT tcp -- anywhere anywhere state NEW tcp dpt:ssh
6 REJECT all -- anywhere anywhere reject-with icmp-host-prohibited
行番号6のルールより前に通信を許可しないと、通信が捨てられてしまいます。
5行目のような(ここではSSHを許可している)ルールを追加していく必要があります。
iptables -I INPUT 6 -p tcp -m state --state NEW -m tcp --dport 6443 -j ACCEPT
iptables -I INPUT 6 -p tcp -m state --state NEW -m tcp --dport 6783 -j ACCEPT
iptables -I INPUT 6 -p tcp -m state --state NEW -m tcp --dport 10250 -j ACCEPT
こんな感じで設定します。
Masterの構築
# kubeadm init --ignore-preflight-errors=swap
swapが有効だとうまくセットアップできないので、あえて無視するように設定します。
コマンドがうまく実行できるとkubeadm joinのスニペットが表示されます。
Workerの構築
# kubeadm join <前手順で生成されるものをコピペ> --ignore-preflight-errors=swap
Workerでは基本的にはスニペットを実行するだけですが、ここでもswapを無視して進めるようにします。
kubectlでのアクセス
kubeconfigをコピーしてきます。(セキュリティ上はあまりよくないですが・・)
# cp /etc/kubernetes/admin.conf /home/ubuntu/
# chmod 644 admin.conf
以降は一般ユーザで作業できます
$ export KUBECONFIG=/home/ubuntu/admin.conf
$ kubectl get nodes
node一覧が表示されれば成功です。
ネットワークアドオンのデプロイ
Pod間の通信を可能にするためにネットワークアドオンであるWeave Netをデプロイします。
$ kubectl apply -f "https://cloud.weave.works/k8s/net?k8s-version=$(kubectl version | base64 | tr -d '\n')"
Ingress Controllerのデプロイ
Webサービスを公開するためにIngress Controllerをデプロイします。
ここでは GitHub - zlabjp/nghttpx-ingress-lb: nghttpx ingress controller for Kubernetes を使うことにします。
まず該当するingressリソースがない場合に表示する404画面用のPodをデプロイします。
apiVersion: apps/v1
kind: Deployment
metadata:
name: default-http-backend
namespace: kube-system
spec:
selector:
matchLabels:
k8s-app: default-http-backend
replicas: 1
template:
metadata:
labels:
k8s-app: default-http-backend
spec:
terminationGracePeriodSeconds: 60
containers:
- name: default-http-backend
# Any image is permissible as long as:
# 1. It serves a 404 page at /
# 2. It serves 200 on a /healthz endpoint
image: k8s.gcr.io/defaultbackend-amd64:1.5
livenessProbe:
httpGet:
path: /healthz
port: 8080
scheme: HTTP
initialDelaySeconds: 30
timeoutSeconds: 5
ports:
- containerPort: 8080
resources:
limits:
cpu: 10m
memory: 20Mi
requests:
cpu: 10m
memory: 20Mi
apiVersion: v1
kind: Service
metadata:
name: default-http-backend
namespace: kube-system
labels:
k8s-app: default-http-backend
spec:
selector:
k8s-app: default-http-backend
ports:
- name: 8080-8080
port: 8080
targetPort: 8080
protocol: TCP
次にingress-controllerが利用するServiceAccountとその権限を設定します。
apiVersion: v1
kind: ServiceAccount
metadata:
name: nghttpx-ingress-serviceaccount
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: nghttpx-ingress-clusterrole
rules:
- apiGroups:
- ""
resources:
- configmaps
- endpoints
- nodes
- pods
- secrets
verbs:
- list
- watch
- apiGroups:
- ""
resources:
- nodes
verbs:
- get
- apiGroups:
- ""
resources:
- services
verbs:
- get
- list
- watch
- apiGroups:
- "networking.k8s.io"
resources:
- ingresses
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- events
verbs:
- create
- patch
- apiGroups:
- "networking.k8s.io"
resources:
- ingresses/status
verbs:
- update
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: nghttpx-ingress-role
namespace: kube-system
rules:
- apiGroups:
- ""
resources:
- configmaps
- pods
- secrets
- namespaces
verbs:
- get
- apiGroups:
- ""
resources:
- configmaps
resourceNames:
# Defaults to "<election-id>-<ingress-class>"
# Here: "<ingress-controller-leader>-<nginx>"
# This has to be adapted if you change either parameter
# when launching the nginx-ingress-controller.
- "ingress-controller-leader-nginx"
verbs:
- get
- update
- apiGroups:
- ""
resources:
- configmaps
verbs:
- create
- apiGroups:
- ""
resources:
- endpoints
verbs:
- get
- create
- update
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: nghttpx-ingress-role-nisa-binding
namespace: kube-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: nghttpx-ingress-role
subjects:
- kind: ServiceAccount
name: nghttpx-ingress-serviceaccount
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: nghttpx-ingress-clusterrole-nisa-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: nghttpx-ingress-clusterrole
subjects:
- kind: ServiceAccount
name: nghttpx-ingress-serviceaccount
namespace: kube-system
最後にingress-controllerをデプロイします。
apiVersion: apps/v1
kind: Deployment
metadata:
name: nghttpx-ingress-controller
namespace: kube-system
labels:
k8s-app: nghttpx-ingress-lb
spec:
selector:
matchLabels:
k8s-app: nghttpx-ingress-lb
template:
metadata:
labels:
k8s-app: nghttpx-ingress-lb
name: nghttpx-ingress-lb
spec:
serviceAccountName: nghttpx-ingress-serviceaccount
terminationGracePeriodSeconds: 60
hostNetwork: true
containers:
- image: zlabjp/nghttpx-ingress-controller:latest
name: nghttpx-ingress-lb
imagePullPolicy: Always
livenessProbe:
httpGet:
path: /healthz
# when changing this port, also specify it using --healthz-port in nghttpx-ingress-controller args.
port: 11249
scheme: HTTP
initialDelaySeconds: 30
timeoutSeconds: 5
# use downward API
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
ports:
- containerPort: 80
hostPort: 80
- containerPort: 443
hostPort: 443
args:
- /nghttpx-ingress-controller
- --default-backend-service=$(POD_NAMESPACE)/default-http-backend
- --healthz-port=11249
- --logtostderr
これで、このクラスタにWebサービスをデプロイする準備が整いました。
追加のネットワーク設定
インターネットからの80番アクセスを許可するために追加でネットワークの設定が必要です。
一番下にある80番ポートについてのルールを追加しました。
また、workerのiptablesも合わせて追加の設定が必要です。
iptables -I INPUT 6 -p tcp -m state --state NEW -m tcp --dport 80 -j ACCEPT
(INPUT 6の”6”はルールを見て適切な行番号を指定する必要があります。)
ここで、手元のPCからworkerのパブリックIPにアクセスをしてみると
このようにそっけない404ページが表示されるはずです。これはKubernetesクラスタ内のPodが返したものです。
Webアプリケーションのデプロイ
最後に、nginxをクラスタ上にデプロイして動作確認をしてみます。
apiVersion: apps/v1
kind: Deployment
metadata:
name: test-nginx
spec:
selector:
matchLabels:
app: nginx
replicas: 1
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: test-service
spec:
ports:
- port: 80
selector:
app: nginx
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: test-ingress
spec:
rules:
- http:
paths:
- backend:
serviceName: test-service
servicePort: 80
これをデプロイしたのちに、先ほどと同様にworkerのパブリックIPにアクセスすると
おめでとう!無事Kubernetesクラスタ上のアプリケーションにインターネットからアクセスできるようになりました。
まとめ
Oracle CloudのAlways Free Tierを使って、Master1台、Worker1台のKubernetesクラスタを作ることができました。
貧弱な構成ですが、無料の範囲内でインターネットからアクセスできるKubernetesクラスタを作れるのは面白いおもちゃになりそうと感じました。
また、Oracle Cloudは初めて使いましたが、普段使っているVPSと遜色なく利用することができ、特段詰まることもありませんでした。