LoginSignup
41
35

More than 5 years have passed since last update.

Amazon EKSでRails API アプリケーションを動かす

Last updated at Posted at 2018-06-19

概要

巷で話題の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 > スタックを選択 > [出力]タブを選択 し、出力を確認する。

ss1.png

EKSクラスタにアクセスするため、認証まわりの設定

.kube/ 作成

$ mkdir -p .kube/

kubeconfigファイル 作成

$ touch ./.kube/k8s-config.yml
.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
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
kubernetes/eks-secret.yml
apiVersion: v1
kind: Secret
metadata:
  name: eks-secret
type: Opaque
data:
  HOGE: aG9nZQ==

dataKEY : 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

kubernetes/eks-deployment.yml
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
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
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
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

41
35
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
41
35