【k8s,EKS】EC2とEKSで構築するKubernetes

#はじめに

・この記事の目的
コンテナとは何か、どうやって使うのかを手を動かして知りたいという方向けに、k8sを使ってウェブアプリを動かせるようになるまでの手順を紹介します。
一からk8sを使えるようにする方法とEKSを使った方法を記載します。

・そもそもk8sって。。。
一言にするとk8sはコンテナオーケストレーションシステムで、コンテナを管理します。
詳しい説明は以下のページがわかりやすいと個人的に思っています。

https://qiita.com/Kta-M/items/ce475c0063d3d3f36d5d

また、k8sで用いるdockerに関しては以下のページが深く説明していると思います。

https://qiita.com/zembutsu/items/24558f9d0d254e33088f

EKSはAWSでk8sをマネージドサービスとして利用できるようにしたものです。

https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/what-is-eks.html

・この記事でやること、作るもの
この記事では、k8sを使える環境について、EC2でクラスターを作成する方法と、EKSでクラスターを作成する方法の２通りを記載します。
最後にk8sを使ってオブジェクトを作成し、ウェブアプリを動かしてみます。

また、すでにk8sの使い方を紹介する記事は多くあるため、この記事では自身が構築する際に注意したところに絞って記載します。

#（方法１）EC2でのクラスター作成手順

##構成図

方法１で使うリソースです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1474054/5b221d75-69b4-2de0-85f5-c7887e6cb080.png)

##１．AWSリソースの作成

k8sをインストールするEC2を作成します。

[過去の記事](https://qiita.com/kokohei39/items/1766652a5a6372f275d3#１スタックファイル作成)で使ったCloudFormationでEC2を作成します。
CloudFormationのスタックはS3にアップロードしたyamlファイルのURLを使って作成できます。

ただし、EC2の性能には注意します。
性能によってはk8sのノード初期化時にエラーが起きます。

```bash:エラー文抜粋
error execution phase preflight: [preflight] Some fatal errors occurred:
        [ERROR NumCPU]: the number of available CPUs 1 is less than the required 2
        [ERROR Mem]: the system RAM (983 MB) is less than the minimum 1700 MB
```

そこで、今回はEC2のインスタンスタイプを「t2.medium」とします。

作成したEC2にSSH接続できるか確認しておきます。
今後、何度も接続するので、コマンドをファイルにまとめて実行します。

この記事では、WindowsでTeratermを使って鍵とEC2のグローバルIPアドレスを指定してアクセスします。EC2にはElasticIPを使っていないのでIPアドレスを入力にします。
コマンドは以下の通りです。

```bat
chcp 65001

set /p EC2_ADDR="input addr of EC2 : "
set TERATERM_EXE="teraterm(.exe)のパス"
set KEY_FILE="鍵(.pem)のパス"

START "" %TERATERM_EXE% %EC2_ADDR% /auth=publickey /user=ec2-user /keyfile=%KEY_FILE%
```

上記をファイル(.bat)に記載して実行するとコマンドプロンプトで、以下の文字が出て入力待機となります。

```
input addr of EC2 : 
```

EC2のグローバルIPを入力してEnterキーを押すと、Teratermが起動してEC2にSSH接続できます。

##２．dockerを実行できる環境の作成

dockerコマンドを使えるようにします。
詳しい手順は以下の記事にあります。

https://qiita.com/reoring/items/0d1f556064d363f0ccb8

EC2にdockerをインストールするには以下の方法もあります。

https://docs.aws.amazon.com/ja_jp/AmazonECR/latest/userguide/getting-started-cli.html

また、この記事ではコンテナのビルドにdocker-composeを使います。
docker-composeのインストール方法は以下の記事にあります。

https://docs.docker.jp/compose/install.html#linux

dockerコマンドをroot以外のユーザー(今回はec2-user)で使うには、dockerグループに加える必要があります。

```bash
sudo usermod -aG docker ec2-user
```

ユーザーのグループ変更を反映するには、再ログインが必要です。
インストールが完了したら、以下のコマンドでdockerのバージョンを確認することができます。

```bash
docker version
docker-compose version
```

##３．k8sを実行できる環境の作成

k8sをインストールして、マスターノードを作成します。
詳しい手順は以下のページにあります。

https://qiita.com/sheepland/items/d63bfe24c8e363132f7a

https://kubernetes.io/ja/docs/setup/production-environment/tools/kubeadm/install-kubeadm/

ノードを作成する際、/etc/hostsファイルにIPアドレスを記載します。
IPアドレスをコマンドで調べて記載するのは以下のコマンドでできます。

```bash
echo "`ip -4 a show eth0 | grep -oP '(?<=inet\s)\d+(\.\d+){3}'` `hostname`" | sudo tee -a /etc/hosts
```

kubelet,kubeadm,kubectlをインストールするコマンドは以下のものを使いました。

```bash
sudo yum install -y kubelet kubeadm kubectl --disableexcludes=kubernetes
```

続いて、kubeletサービスを起動しますが、kubeadmでノードを初期化するまではエラーが起きます。

```bash
＜コマンド＞
systemctl status kubelet

＜コマンド結果＞
● kubelet.service - kubelet: The Kubernetes Node Agent
   Loaded: loaded (/usr/lib/systemd/system/kubelet.service; enabled; vendor preset: disabled)
  Drop-In: /usr/lib/systemd/system/kubelet.service.d
           mq10-kubeadm.conf
   Active: activating (auto-restart) (Result: exit-code) since Fri 2021-12-03 14:25:06 UTC; 1s ago
     Docs: https://kubernetes.io/docs/
  Process: 2098 ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARGS (code=exited, status=1/FAILURE)
 Main PID: 2098 (code=exited, status=1/FAILURE)
```

次にノードを初期化しますが、以下のエラーが起きることがあります。

```bash

＜コマンド＞
sudo kubeadm init

＜エラー文の確認＞
sudo tail /var/log/messages

＜エラー文抜粋＞
Dec  3 14:35:53 ip-10-0-0-105 kubelet: E1203 14:35:53.407991    6172 server.go:294] "Failed to run kubelet" err="failed to run Kubelet: misconfiguration: kubelet cgroup driver: \"systemd\" is different from docker cgroup driver: \"cgroupfs\""
Dec  3 14:35:53 ip-10-0-0-105 systemd: kubelet.service: main process exited, code=exited, status=1/FAILURE
Dec  3 14:35:53 ip-10-0-0-105 systemd: Unit kubelet.service entered failed state.
Dec  3 14:35:53 ip-10-0-0-105 systemd: kubelet.service failed.
```

これを解決するため、以下の記事を参考に作業します。

https://kubernetes.io/ja/docs/setup/production-environment/container-runtimes/

https://qiita.com/rottneer/items/5dad5193b1577c4edc29

dockerのコントロールグループのファイルシステムを変更します。

```bash
echo '
{
  "exec-opts": ["native.cgroupdriver=systemd"]
}
' | sudo tee /etc/docker/daemon.json > /dev/null
```

変更後、サービスの再起動を行います。

```bash
sudo systemctl daemon-reload
sudo systemctl restart docker
sudo systemctl restart kubelet
```

これで「sudo kubeadm init」がエラーなく実行できるようになります。
後はコマンドの案内に従ってkubeconfigファイルを作成します。

インストールしたパッケージは以下のコマンドでバージョンを確認できます。

```bash
kubelet --version
kubeadm version
kubectl version
```

##４．k8sのクラスターネットワークの作成

最後にクラスターネットワークを作成します。
詳細は以下の記事にあります。

https://qiita.com/sheepland/items/d63bfe24c8e363132f7a#ネットワークの作成

#（方法２）EKSでのクラスター作成手順

##構成図

方法２で使うリソースです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1474054/ec4064ea-4461-9f80-4cec-3fdfd64531b3.png)

##１．AWSリソースの作成

EKSを使うためのVPCやサブネット、EKSにアクセスするためのEC2を作成します。
CloudFormationを利用します。
EKSは異なるAZの２つのサブネットが必要なので、方法1よりも作成するリソースは多くなります。
EKSが利用するサブネットのyamlを以下に示します。

```yaml
#SUBNET
  SnPublic:
    Type: 'AWS::EC2::Subnet'
    Properties:
      VpcId: !Ref vpc
      CidrBlock: 10.0.0.0/24
      AvailabilityZone: ap-northeast-1a
      Tags:
        - Key: Name
          Value: !Sub '${AWS::StackName}-SnPublic'

  SnPrivate:
    Type: 'AWS::EC2::Subnet'
    Properties:
      VpcId: !Ref vpc
      CidrBlock: 10.0.1.0/24
      AvailabilityZone: ap-northeast-1c
      Tags:
        - Key: Name
          Value: !Sub '${AWS::StackName}-SnPrivate'
```

##２．EKSクラスターの作成

AWSコンソールからEKSを作成します。
作成するためにコンソール上で以下の項目を入力します。

|項目  |値  |
|---|---|
|名前  |任意の名前を入力  |
|クラスターサービスロール  |適したロールを選択  |
|サブネット  |作成した２つのサブネット  |
|クラスターエンドポイントアクセス  |プライベート  |

EKSが作成されると自動で、EKSが所属するセキュリティグループ(SG)も自動で作成されます。
作成されたSGはクラスターのネットワーキングのページで確認できます。
EC2からアクセスできるように、セキュリティグループのインバウンドルールに以下を追加します。

|項目  |値  |
|---|---|
|タイプ  |すべてのTCP  |
|ソース  |EC2のローカルIPアドレス  |

EKS作成で必要なIAMロールは以下のページから確認できます。

https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/service_IAM_role.html

※今回はノードグループは使いませんが、作成する場合には以下のページに説明のあるIAMロールが必要です。

https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/create-node-role.html

##３．dockerを実行できる環境の作成

EKSへアクセスするEC2でdockerのコマンドを使えるようにします。
[方法１](#２dockerを実行できる環境の作成)と同様の手順です。

##４．k8sコマンドを実行できる環境の作成

EKSへアクセスするEC2でk8sのコマンドを使えるようにします。

k8sコマンドを使うにはkubectlをインストールします。
詳しい手順は以下のページにあります。

https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/install-kubectl.html

どのバージョンのkubectlをインストールするかは、k8sのバージョンとEC2のCPUのベンダーから判断します。
k8sのバージョンはEKSのページから確認できます。
CPUのベンダーは以下のコマンドで表示されるvender_idから判断します。

```bash
cat /proc/cpuinfo
```

今回はk8sのバージョンが1.21でCPUのベンダーがIntelなので、以下のコマンドでkubectlをインストールします。

```bash
curl -o kubectl https://amazon-eks.s3.us-west-2.amazonaws.com/1.21.2/2021-07-05/bin/linux/amd64/kubectl
```

次に、awsコマンドからEKSに接続するための設定をします。
awsコマンドを使うにはアクセスキーの設定をします。コマンド「aws configure」で設定できますが、今回はファイルへ直接書き込みます。

```bash
mkdir -p /home/ec2-user/.aws
echo "
[default]
output = json
region = ap-northeast-1
" > /home/ec2-user/.aws/config
echo "
[default]
aws_access_key_id = awsのアクセスキー
aws_secret_access_key = awsのシークレットアクセスキー
" > /home/ec2-user/.aws/credentials
```

EKSに接続するためには、接続先を記したkubeconfigファイルを作成します。
詳細な手順は以下のページにあります。

https://docs.aws.amazon.com/eks/latest/userguide/create-kubeconfig.html

$HOME/.kube/configファイルには以下の情報が書かれています。

・クラスター(clusters)
・ユーザー(users)
・コンテキスト(contexts)
・現在設定されているコンテキスト(current-context)

現在設定されているコンテキストを表示するには以下のコマンドを使用します。

```bash
kubectl config get-contexts
```

##５．ECRの作成と接続

k8sで使うコンテナイメージの保存先であるECRの作成とEC2からの接続を行います。

まず、ECRをAWSコンソールから作成します。
ECRの作成ページから以下の項目を入力して作成します。

|項目  |値  |
|---|---|
|可視性設定  |プライベート  |
|リポジトリ名  |任意の名前を入力  |

次にECRへ接続します。
詳細な手順は以下のページにあります。

https://docs.aws.amazon.com/ja_jp/AmazonECR/latest/userguide/getting-started-cli.html

ECRへ接続するための情報は/home/ec2-user/.docker/config.jsonに記載されます。
ECRへコンテナイメージをプッシュする方法は以下のページにあります。

https://docs.aws.amazon.com/ja_jp/AmazonECR/latest/userguide/docker-push-ecr-image.html

#k8sでウェブアプリの実行とアクセス

k8sにオブジェクトを作成し、コンテナ内でウェブアプリを動かしてみます。
今回は、[方法1](#方法１ec2でのクラスター作成手順)で作成したEC2内で作業します。
動かすウェブアプリは[過去の記事](https://qiita.com/kokohei39/items/1766652a5a6372f275d3#４unityアプリのビルド)で作成したものを使います。

##１．dockerのコンテナイメージの作成

コンテナのイメージはcentosを基に作成します。そのため、以下のコマンドでEC2にcentosのイメージをダウンロードします。

```bash
docker pull centos:centos7
```

ビルド時の動作を記述するDockerfileを作成します。
Dockerfileの詳細は以下のページにあります。

https://qiita.com/wasanx25/items/d47caf37b79e855af95f

Dockerfileにはソースファイルをコピーする処理を記述します。
ファイルはすべて/home/ec2-user/sourceディレクトリに入っているものとします。

```Dockerfile
ENV workdir /myapp
ENV srcdir .
ADD $srcdir $workdir
```

docker-composeで使うyamlファイルを作成します。

```yaml:docker-compose.yml
version: "3"
services:
  web:
    build: .
    image: centos:myimg
    tty: true
```

docker-composeコマンドでビルドします。

```bash
docker-compose build
```

※ビルド後は以下のコマンドでコンテナを立ち上げることができます。

```bash
docker-compose up -d
```

##２．k8sでのウェブアプリの実行

k8sでオブジェクトを作成し、ウェブアプリを実行します。
この記事ではマスターノードにオブジェクトを作成します。
そのため、マスターノードへのPODのデプロイを許可します。

```bash
MASTER_NODE=`kubectl get node | grep master | cut -d' ' -f1`
kubectl taint nodes $MASTER_NODE node-role.kubernetes.io/master:NoSchedule-
```

詳細な手順は以下のページにあります。

https://qiita.com/nykym/items/dcc572c21885543d94c8

次にPODをデプロイするためのyamlファイルを作成します。

```yaml:k8s-manifest.yml
apiVersion: v1
kind: Pod
metadata:
  name: mypod
  labels:
    app: webapp
spec:
  containers:
  - name: webapp
    image: centos:myimg
    tty: true
    ports:
    - containerPort: 8080
```

yamlファイルを基にPODを作成します。

```bash
kubectl apply -f 作成したyamlファイル
```

POD内のウェブアプリをk8s外からアクセスするためのserviceオブジェクトを作成します。
yamlファイルは以下の通りです。serviceオブジェクト作成のコマンドはPOD作成時と同じものとなります。

```yaml:k8s-service.yml
kind: Service
apiVersion: v1
metadata:
  name: myservice
spec:
  ports:
    - name: webapp
      port: 8080
      nodePort: 30001
  selector:
    app: webapp
  type: LoadBalancer
```

作成したPODにログインして、ウェブアプリを実行します。
以下のコマンドでPODにログインします。

```
kubectl exec -it mypod -- /bin/bash
```

ウェブアプリの実行手順は[過去の記事](https://qiita.com/kokohei39/items/1766652a5a6372f275d3#８シェルスクリプト作成)を基に記載します。
まず、以下のコマンドでjavaをインストールします。

```bash
#javaインストールのために必要なパッケージのインストール
sudo yum install -y wget

#javaのインストール
mkdir -p $HOME/java-tmp/jdk
mkdir -p $HOME/.java/jdk
wget https://download.java.net/java/GA/jdk11/9/GPL/openjdk-11.0.2_linux-x64_bin.tar.gz -O $HOME/java-tmp/jdk-pkg
tar xzvf $HOME/java-tmp/jdk-pkg -C $HOME/java-tmp/jdk
cp -r $HOME/java-tmp/jdk/`ls $HOME/java-tmp/jdk/`/* $HOME/.java/jdk
export JAVA_HOME=$HOME/.java/jdk
export PATH=$PATH:$JAVA_HOME/bin
echo '
export JAVA_HOME=$HOME/.java/jdk
export PATH=$PATH:$JAVA_HOME/bin
' >> $HOME/.bashrc
yes | rm -r $HOME/java-tmp
```

以下のコマンドでウェブアプリを実行します。

```bash
java -jar jarファイル
```

ウェブアプリが動いているかの確認は、EC2上とEC2にログインするために使っているローカルPC上で行えます。

EC2上で行うには、EC2にログイン後、以下のコマンドを実行します。
ステータスコードが200で帰ってくると思います。

```bash
curl -I http://localhost:30001
```

ローカルPC上で行う方法は以下の通りです。
Windowsの場合はコマンドプロンプトで以下を実行します。

```bat
ssh -i pemファイル -L 18080:localhost:30001 ec2-user@EC2のグローバルIPアドレス -N
```

次にブラウザにて、以下のURLにアクセスするとウェブアプリにアクセスできることが確認できます。
http://localhost:18080

#終わりに

今回は業務でKubernetes(k8s)に触れる機会があり、学んだことと気になったことをまとめる意味で記事を作成しました。
k8sの構築について、EC2の方法と、EKSの方法を比較すると、EKSの方が手を動かす量は少なく感じました。
しかし、EKSを使う場合にはIAMロールを考える必要があり、EC2の方が必要なAWSのリソースは少ないと感じました。

学べば学ぶほどk8sは深いなーと感じました。
手を動かすと知識を深く学ぶことができて、楽しいです！