概要
巷で話題のAmazon EKSを使ってRailsアプリを動かしてみました。
チュートリアルに毛が生えたくらいの内容です。
EKS とは
AWSでやってるkubernetesのマネージドサービス。
以下のリージョンで利用可能。(2018.06.18現在)
- 米国西部 (オレゴン) (us-west-2)
- 米国東部 (バージニア北部) (us-east-1)
載せるアプリ
- Rails : 5.2.0
- Ruby : 2.5.1
- DB : PostgreSQL
今回、DBにはRDSを使用した。
しかし、ストレージクラスというものを使うとkubernetes上でストレージを用意できるっぽい。
これを使うとDBのような永続化が必要なサービスも動かすことができる。
必要なもの
- バージョン1.15.32以降のAWSCLI
- Amazon EKSを触るのに必要
- brew版は
1.15.32以降
の要件が満たせず、pipで再度インストールした(2018.06.18現在)
-
kubectl
- Kubernetesクラスタに対してコマンドを実行するためのCLI
- Homebrewでインストールした
-
heptio-authenticator-aws
- IAMの情報を使ってKubernetesクラスタへの認証を行うためのツール
-
go get
でgithubからインストールした
リソースの作成
ベースにした構成
こちらのcfn.ymlを パクった 参考にした。
https://github.com/y13i/aws-eks-example
cfn.ymlを拝借し、カスタマイズして使用した。
その節はお世話になりました。ありがとうございました。
このファイルは、EKSの利用に必要なリソースすべてをひとつのCloudFormationスタックで作成するようにしていて、さらにネットワーク構成をあるあるな形にしたものになっている。
ざっくりこんな感じ。
- VPC
- パブリックサブネット*2
- プライベートサブネット*2
- プライベートサブネット内にアプリ用のインスタンス設置
- EKSクラスタの作成
カスタマイズ的な部分
Railsアプリを動かすのに、さらに以下のものが必要だったためyamlに追記した
- RDS
- プライベートサブネット内に設置
- ECRのリポジトリ作成
- S3の設定
Parameters: の追加分
DBUsername:
Type: String
DBPassword:
Type: String
NoEcho: true
Resources: の追加分
VPCEndpointForS3:
Type: AWS::EC2::VPCEndpoint
Properties:
RouteTableIds:
- Ref: PublicRouteTable
- Ref: PrivateRouteTable0
- Ref: PrivateRouteTable1
VpcId:
Ref: Vpc
ServiceName:
Fn::Join:
- "."
- - com
- amazonaws
- Ref: AWS::Region
- s3
DBSubnetGroup:
Type: AWS::RDS::DBSubnetGroup
Properties:
DBSubnetGroupDescription:
Ref: AWS::StackName
SubnetIds:
- Ref: PrivateSubnet0
- Ref: PrivateSubnet1
Tags:
- Key: Name
Value:
Ref: AWS::StackName
DBParameterGroup:
Type: AWS::RDS::DBParameterGroup
Properties:
Family: postgres9.6
Description:
Ref: AWS::StackName
Parameters:
client_encoding: UTF8
timezone: Asia/Tokyo
DBInstance:
Type: "AWS::RDS::DBInstance"
Properties:
AllocatedStorage: 20
DBInstanceClass: db.t2.micro
DBName:
Ref: AWS::StackName
DBParameterGroupName:
Ref: DBParameterGroup
DBSubnetGroupName:
Ref: DBSubnetGroup
Engine: postgres
EngineVersion: "9.6.6"
MasterUsername:
Ref: DBUsername
MasterUserPassword:
Ref: DBPassword
MultiAZ: true
PubliclyAccessible: false
StorageType: gp2
Tags:
- Key: Name
Value:
Ref: AWS::StackName
VPCSecurityGroups:
- Fn::GetAtt:
- DBSecurityGroup
- GroupId
DBSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: for rds
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 5432
ToPort: 5432
SourceSecurityGroupId:
Fn::GetAtt:
- NodeSecurityGroup
- GroupId
VpcId:
Ref: Vpc
Tags:
- Key: Name
Value:
Ref: AWS::StackName
ContainerRepository:
Type: AWS::ECR::Repository
LogGroup:
Type: AWS::Logs::LogGroup
Properties:
RetentionInDays: 365
LogGroupName:
Ref: AWS::StackName
StorageBucket:
Type: AWS::S3::Bucket
StorageUser:
Type: AWS::IAM::User
Properties:
ManagedPolicyArns:
- Ref: StorageUserPolicy
StorageAccessKey:
Type: AWS::IAM::AccessKey
Properties:
UserName:
Ref: StorageUser
StorageUserPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action: s3:*
Resource:
- Fn::GetAtt:
- StorageBucket
- Arn
- Fn::Join:
- ""
- - Fn::GetAtt:
- StorageBucket
- Arn
- /*
-
Outputs: の追加分
ContainerRepository:
Value:
Ref: ContainerRepository
DBHostName:
Value:
Fn::GetAtt:
- DBInstance
- Endpoint.Address
DBPort:
Value:
Fn::GetAtt:
- DBInstance
- Endpoint.Port
準備ができたのでデプロイする
ディレクトリ構造はこんな感じ。
api/
├ app/
├ config/
│ .
│ .
│ .
├ infra/
│ └ cfn.yml
└ Dockerfile
apiディレクトリにてコマンド実行
※完了まで15〜20分くらい
$ aws cloudformation deploy --template-file infra/cfn.yml --capabilities CAPABILITY_IAM --stack-name development --parameter-overrides DBUsername=app DBPassword=hogehoge
デプロイが完了したら、マネジメントコンソールから
CloudFormation > スタックを選択 > [出力]タブを選択 し、出力を確認する。
EKSクラスタにアクセスするため、認証まわりの設定
.kube/ 作成
$ mkdir -p .kube/
kubeconfigファイル 作成
$ touch ./.kube/k8s-config.yml
apiVersion: v1
clusters:
- cluster:
server: <CFnで出力された EKSClusterEndpoint>
certificate-authority-data: <CFnで出力された EKSClusterCertificateAuthorityData>
name: kubernetes
contexts:
- context:
cluster: kubernetes
user: aws
name: aws
current-context: aws
kind: Config
preferences: {}
users:
- name: aws
user:
exec:
apiVersion: client.authentication.k8s.io/v1alpha1
command: heptio-authenticator-aws
args:
- "token"
- "-i"
- <CFnで出力された EKSClusterName>
環境変数KUBECONFIGの設定
kubectl コマンド実行時にk8s-config.ymlを参照するようパスを追加する。
direnv なり .bash_profile なりに追記。
export KUBECONFIG=<k8s-config.ymlへのパス>
クラスタにアクセスしてみる
$ kubectl get all
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP xxx.xx.x.x <none> 443/TCP 8m
※ command: heptio-authenticator-aws
関連でエラーが出る場合は、 k8s-config.yml
からheptio-authenticator-awsまでの相対パスに書き換えてください
例) ../../../go/bin/heptio-authenticator-aws
ワーカノードの設定
kubernetesの設定ファイル置き場を用意
$ mkdir ./kubernetes/
設定ファイルの作成
$ touch kubernetes/aws-auth-configmap.yml
apiVersion: v1
kind: ConfigMap
metadata:
name: aws-auth
namespace: kube-system
data:
mapRoles: |
- rolearn: <CFnで出力された NodeInstanceRole>
username: system:node:{{EC2PrivateDNSName}}
groups:
- system:bootstrappers
- system:nodes
クラスタにnodeを参加させる
$ kubectl apply -f kubernetes/aws-auth-configmap.yml
configmap "aws-auth" created
node一覧を取得し、確認してみる
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
ip-XX-X-XX-XXX.ec2.internal Ready <none> 1m v1.10.3
ip-XX-X-XX-XX.ec2.internal Ready <none> 51s v1.10.3
STATUSがReadyになっていなかったらReadyになるまで温かく見守る
$ kubectl get nodes --watch
環境変数設定
direnv でも .bash_profile でも好きなのにどうぞ。
export AWS_ACCOUNT_ID=xxxxx
export AWS_DEFAULT_REGION=us-east-1
export AWS_REGION=us-east-1
export AWS_ACCESS_KEY_ID=xxxxx
export AWS_SECRET_ACCESS_KEY=xxxxxx
export CONTAINER_REPOSITORY=<CFnで出力された ContainerRepository>
export CONTAINER_IMAGE_TAG=<適当なタグ>
ECRへログイン、イメージのbuild、タグ付け、プッシュ
$ $(aws ecr get-login --no-include-email)
$ docker build -t "$CONTAINER_REPOSITORY:$CONTAINER_IMAGE_TAG" .
$ docker tag "$CONTAINER_REPOSITORY:$CONTAINER_IMAGE_TAG" "$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$CONTAINER_REPOSITORY:$CONTAINER_IMAGE_TAG"
$ docker push "$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$CONTAINER_REPOSITORY:$CONTAINER_IMAGE_TAG"
kubernetesのマニフェスト作成
secretの作成
Podで環境変数を使うために用意してる。
$ touch kubernetes/eks-secret.yml
apiVersion: v1
kind: Secret
metadata:
name: eks-secret
type: Opaque
data:
HOGE: aG9nZQ==
data
は KEY : VALUE
のペアで、VALUEはbase64エンコードした値。
以下のようにコマンドで生成したものをコピペしてる。
$ echo -n "hoge" | base64
aG9nZQ==
この場合、Podでは値が hoge
である環境変数 HOGE
が使える状態になる。
作成したマニフェストをクラスタに適用する。
$ kubectl create -f kubernetes/eks-secret.yml
deploymentの作成
Pods と ReplicaSets の設定がまとめられたもの。
$ touch kubernetes/eks-deployment.yml
ECRのイメージ情報を取得しておく
$ echo "$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$CONTAINER_REPOSITORY:$CONTAINER_IMAGE_TAG"
xxxxx.dkr.ecr.us-east-1.amazonaws.com/xxxxx:xxxxx
取得したイメージ情報を image:
につっこむ
URLからタグまでフルで指定する必要がある。(Using AWS EC2 Container Registry)
apiVersion: apps/v1
kind: Deployment
metadata:
name: eks-deployment
spec:
replicas: 3
selector:
matchLabels:
app: rails-api
template:
metadata:
labels:
app: rails-api
spec:
containers:
- name: rails-api
image: xxxxx.dkr.ecr.us-east-1.amazonaws.com/xxxxx:xxxxx
ports:
- containerPort: 3000
envFrom:
- secretRef:
name: eks-secret
args:
- rails
- server
- -p
- '3000'
- -b
- '0.0.0.0'
これは以下のような意味を持つ。
-
xxxxx.dkr.ecr.us-east-1.amazonaws.com/xxxxx:xxxxx
イメージをもとにしたコンテナが入ったPodを作る。 - そのPodにラベル
app=rails-api
を付与する。 - ラベル
app=rails-api
がついたPodが3つになるようレプリケーションする。 - Podは外部通信用に3000ポートを開ける。
- コンテナ作成時に
rails server -p '3000' -b '0.0.0.0'
を実行する。
作成したマニフェストをクラスタに適用する。
$ kubectl create -f kubernetes/eks-deployment.yml
serviceの作成
Podを置いてアプリが動くのは良いとして、そのアプリにどうやってアクセスするの? を解決するのがservice。
ユーザーからのリクエストを処理するのにこいつが必要。
$ touch kubernetes/eks-service.yml
kind: Service
apiVersion: v1
metadata:
name: eks-service
spec:
type: LoadBalancer
selector:
app: rails-api
ports:
- protocol: TCP
port: 80
targetPort: 3000
これは以下のような意味をもつ。
- 80ポートへアクセスがあったとき、ラベルが
app=rails-api
のPodの3000ポートにつなぐ
作成したマニフェストをクラスタに適用する。
$ kubectl create -f kubernetes/eks-service.yml
jobの作成
アプリのように常に動かしたいものではなく、なにかを実行したら消えて欲しいものにはjobを使う。
例えばRailsの場合、 rails db:create
とか rails db:migrate
みたいなのは一度だけ実行したいものだと思う。
そのためにjobを用意した。
$ touch kubernetes/eks-create-job.yml
apiVersion: batch/v1
kind: Job
metadata:
name: eks-create-job
spec:
backoffLimit: 3
parallelism: 1
completions: 1
template:
spec:
containers:
- name: rails-create
image: xxxxx.dkr.ecr.us-east-1.amazonaws.com/xxxxx:xxxxx
ports:
- containerPort: 3000
envFrom:
- secretRef:
name: eks-secret
args:
- rails
- db:create
restartPolicy: Never
Podの部分はdeploymentと同じで、arg(=実行するコマンド)が違う。
意味としては
-
xxxxx.dkr.ecr.us-east-1.amazonaws.com/xxxxx:xxxxx
イメージをもとにしたコンテナが入ったPodを作る。 - Podは外部通信用に3000ポートを開ける。
- コンテナ作成時に
rails db:create
を実行する。 - 実行が失敗したら3回までリトライする。
- Podは最大1つ実行する。
- 1つのPodの処理が成功したらjobの成功とする。
- コンテナの再起動は行わない。
作成したマニフェストをクラスタに適用する。
$ kubectl create -f kubernetes/eks-create-job.yml
migrateのほうも同様です。
$ touch kubernetes/eks-migrate-job.yml
apiVersion: batch/v1
kind: Job
metadata:
name: eks-migrate-job
spec:
backoffLimit: 3
parallelism: 1
completions: 1
template:
spec:
containers:
- name: rails-migrate
image: xxxxx.dkr.ecr.us-east-1.amazonaws.com/xxxxx:xxxxx
ports:
- containerPort: 3000
envFrom:
- secretRef:
name: eks-secret
args:
- rails
- db:migrate
restartPolicy: Never
作成したマニフェストをクラスタに適用する。
$ kubectl create -f kubernetes/eks-migrate-job.yml
アプリにアクセスする
serviceを作成すると、EKSによって自動でロードバランサが作成される。
そこにアクセスしたいので、確認するために以下のコマンドを実行する。
$ kubectl describe services
その中から以下の項目を見つける。
LoadBalancer Ingress: xxxxxxxxxx.us-east-1.elb.amazonaws.com
xxxxxxxxxx.us-east-1.elb.amazonaws.com
がエンドポイントとなる。
ブラウザやPostmanなんかでアクセス可能になる。
参考URL
Deploy a Kubernetes Application with Amazon Elastic Container Service for Kubernetes