はじめに
様々なツールを使ってEC2上へk8sを導入しました。
以下に手順(README.md
)、コードをアップロードしています。
https://github.com/not75743/k8s_madeby_packer_terraform_ansible_kubeadm
~1時間で作成可能であるため、k8sを試して見たい方は覗いてみてください。
今回の記事では手順作成でつまずいた箇所、注目すべき箇所をメモしたいと思います。
※本投稿では手順の詳細を記載しません。
詳細については上記のリンクよりREADME.md
を参照ください。
作れるもの
- k8sクラスタ
- master *1
- worker *1~3
構成図は以下となります。
対象読者
- とりあえずk8sを試してみたい。
- ローカル環境にサーバがないから利用したい。
- ツール(packer,ansible,terraform)の使い方について参考にしたい
クラスタ作成の流れ
ざっくり以下になります
- 環境を用意する
- packerでk8sノードのイメージ作成
- terraformでAWS環境にリソース作成
- ansibleでmasterノード用意
- ansibleでworkerノード用意
※ 3~5はシェルスクリプトを使ってほぼ自動で実施するようにしています。
この項目ごとにポイントをピックアップしていきたいと思います。
今回はkubeadmでクラスタを用意しており、各手順で以下を参考にしました。
https://kubernetes.io/ja/docs/setup/production-environment/tools/kubeadm/install-kubeadm/
https://kubernetes.io/ja/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/
1.環境を用意する
もろもろのツールをインストールします。すでにあれば不要です。
dockerfile
dockerfileを用意しているため、用意に環境を準備可能です。
centos7をベースイメージとしています。
2.packerでk8sノードのイメージ作成
master,workerの共通AMIを作成するためにpackerを使用します。
AMIを作っておくことで2回目以降のクラスタ作成時間を短縮することが可能です。
AMI構築にはAnsible Remote Rrovisionerを利用しました。
https://www.packer.io/docs/provisioners/ansible/ansible
AWS用のクレデンシャルが不足する
packer build実行時、以下の様な出力が出ました。
no valid credential sources for found
no valid credential
とある通り、AWS用のcredentialが読み込めていません。
ローカルにcredentialを残さないため、今回は環境変数でcredentialを設定します。
export AWS_ACCESS_KEY_ID="xxxxxxxxxxxxxxxxxx"
export AWS_SECRET_ACCESS_KEY="xxxxxxxxxxxxxxxxxxx"
参考は以下
https://www.packer.io/docs/builders/amazon#authentication
sftpエラーとなる
packer build実行中にansibleが実行できなかった旨のログが出力されました。
amazon-ebs.centos: fatal: [kubenode]: FAILED! => {"msg": "failed to open a SFTP connection (EOF during negotiation)"}
Ansible Provisionerはリモートサーバ上で/usr/lib/sftp-server -e
を実行し、ローカルとsftpでやり取りします。
CentOS7のベースAMIでは上記パスにsftpが存在しないため、以下の様にパスを書き換える必要があります。
sftp_command = "/usr/libexec/openssh/sftp-server -e"
参考は以下
https://www.packer.io/docs/provisioners/ansible/ansible#sftp_command
https://www.packer.io/docs/provisioners/ansible/ansible#redhat-centos
packer -v が出力されない
packer -v を実行してもバージョンが出力されず、出力待ちでプロンプトが停止しました。
原因は以下のpackerを実行しているからでした
/usr/sbin/packer
実態はcracklib-packer
というパスワード正常性チェッカーでした。
今回は使わないのでdockerfileビルド時にシンボリックリンクを削除しています。
$ ll /usr/sbin/packer
lrwxrwxrwx. 1 root root 15 12月 29 2020 /usr/sbin/packer -> cracklib-packer
参考は以下
https://qiita.com/0xmks/items/2513745d927941869d64
https://www.kabegiwablog.com/entry/2018/02/02/090000
https://github.com/cracklib/cracklib/issues/7
3.terraformでAWS環境にリソース作成
k8sノードを含めたAWSリソース作成にterraformを使用しました。
terraformを使うことで容易な環境作成、破棄が可能となります。
outputをansibleのhostsファイルに記載
(正確にはterraformでなくshellですが)本手順ではEC2のpublic ipを取得するためにmain.tf
内でoutputを設定し、それを各ファイルにsedで転記しています。
「sedがipアドレス追記に使える!」と思いスクリプトに反映させましたが、調べてみるとほかにも手段がありそうなので、今後調べてみたいです。
EC2 inventory source
PRODUCE AN ANSIBLE INVENTORY WITH TERRAFORM
セキュリティグループは指定IPのみ許可
セキュリティグループは以下の様に接続元IPを記載したIPのみに絞ります。
resource "aws_security_group_rule" "worker_ssh" {
type = "ingress"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = var.ssh_to_worker
security_group_id = aws_security_group.kube_worker-sg.id # ここ
}
terraform.tfvars
でホストのIPを忘れずに記載しましょう。
ノードの数を指定
workerのEC2の個数を1~3にするために以下のようにコードを書きました。
resource "aws_instance" "worker" {
count = (var.worker_count == 0 ? 1 : (var.worker_count < 4 ? var.worker_count : 3)) # ここ
いわゆるif文です。
入れ子にし、0,4以上を選択した場合にそれぞれ1,3に置き換える様にしています。
0だとk8sの意味がなくなる、4以上は結構な課金がされる、ということを考慮して書きました。
見づらい(気がする?)ため、もしほかにやり方が合ったらご教授いただけると幸いです。
参考は以下
https://www.terraform.io/docs/language/expressions/conditionals.html
4.ansibleでmasterノード用意
terraformで用意したノードにSSH接続し、ansibleでkubeadm init
を実行します。
--apiserver-cert-extra-sans={{ masterip }}
k8sのapiserverにSAN(Subject Alternative Name)を追加します。
オプションなしkubeadm init
で生成されるapiserver証明書のSANにはバブリックIPの記載がなく(CommonNameにもなく)、バブリックIP指定での接続が不可であるため本設定を追加します。
[root@ip-10-0-1-100 ~]# openssl x509 -text -noout -in /etc/kubernetes/pki/apiserver.crt | grep -A1 "X509v3 Subject Alternative Name:"
X509v3 Subject Alternative Name:
DNS:ip-10-0-1-100.ap-northeast-1.compute.internal, DNS:kubernetes, DNS:kubernetes.default, DNS:kubernetes.default.svc, DNS:kubernetes.default.svc.cluster.local, IP Address:10.96.0.1, IP Address:10.0.1.100, IP Address:3.113.29.168 # ここの一番後ろに追加されている
参考は以下
https://stackoverflow.com/questions/46360361/invalid-x509-certificate-for-kubernetes-master
https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm-init/
https://kubernetes.io/ja/docs/setup/best-practices/certificates/
get token & token-ca-cert-hash
それぞれworkerがk8sに参加するkubeadm join 時に必要になります。
masterのplaybook実行時にそれぞれ拾い、それをlineinfile
モジュールで変数ファイルに記載し、workerのplaybookで参照できるようにします。
- name: get token
shell: kubeadm token list | awk 'NR==2{print $1}'
register: token
- name: replace worker var "token"
lineinfile:
path: ./vars/vars.yaml
regexp: "^token:"
line: "token: {{ token.stdout }} "
state: present
delegate_to: localhost
become: no
lineinfile
はローカルで実行するためdelegate_to
でローカルを選択します。
参考は以下
https://qiita.com/fumiya-konno/items/d2d6b67296e5ee94340b
https://docs.ansible.com/ansible/latest/user_guide/playbooks_delegation.html#id3
kubeconfig作成
k8sのAPIサーバと通信するためにkubeconfigをローカルに持ってきます。
持っていく先は~/.kube/configにすると、kubectl時に自動で反映されるため楽です。
すでにk8sを使っているユーザの場合、既存kubeconfigを上書きしてしまうと困るため、今回は作業ディレクトリ内にコピーし、そのパスを環境変数KUBECONFIG
で指定するようにしています。
参考は以下
https://kubernetes.io/ja/docs/setup/production-environment/tools/kubeadm/_print/
https://kubernetes.io/ja/docs/concepts/configuration/organize-cluster-access-kubeconfig/#kubeconfig%E7%92%B0%E5%A2%83%E5%A4%89%E6%95%B0
5.ansibleでworkerノード用意
masterのplaybookが終了したらworkerのplaybookを実行します。
とはいってもkubeadm joinを実行するだけです。
playbook実行時の「Are you sure you want to continue connecting」を防ぐ
masterもworkerも初めてSSH接続するノードであるため、playbook実行前に接続するか問われます。
The authenticity of host 'xxxx.com (xxx.xxx.xxx.xxx)' can't be established.
RSA key fingerprint is xx:xx:xx:xx...
Are you sure you want to continue connecting (yes/no)?
ansibleで接続できなくなるため、cfgファイルにhost_key_checking = False
を記載し、playbook実行前に環境変数で指定します。
export ANSIBLE_CONFIG=./cfg/ansible.cfg
[defaults]
host_key_checking = False # to avoid ssh check
参考は以下
https://docs.ansible.com/ansible/latest/reference_appendices/config.html#host-key-checking
おわりに
大変でしたが、1から作って各コンポーネントの理解を深められたと思います。
以下の様な課題がまだあるため、これから手を付けていきたいところです。
-
packer,terraform,ansibleでファイル、ディレクトリ構成をあまり意識していなかった(roleで分ける等)
-
kubeadm initで何しているのか追い切れていない。
-
terraform destroy を忘れると課金が止まらない。(監視の仕組みを作りたい)