のほほんおじさんのアウトプット

インフラエンジニア。 マリノス・美術館・編集工学・AWS・ウイスキー・いぶりがっこ・タルタルが好きです。好き勝手やっていこうかなって気持ちです。

ECS(Fargate)でJMeterを構築してみる

ECS(Fargate)でJMeterを構築するっていうタスクに従事中

  • タスクを振られたのでなんとか対応せねばなりません
  • しかも、以下のような縛りがあります
    • JMeterでシナリオ監視をせよ
    • ECS、しかもFargateで構築せよ
  • ちなみに、ECSやったことない、、、一応AWSのコンテナサービスだってことくらいは知っているくらい
  • あと、JMeterもあのWEBサイトに試験的に負荷をかけるアレでしょ、程度の知識しかない、、、

とりあえずググる

  • まず出てきたのは以下の記事

tkaaad97.hatenablog.com

  • ドンピシャかと思いきやFargateではなくEC2でクラスタを構築するとのこと、、、
  • でも、上記の記事はとても参考になりました。50回くらいは読み込みました!

必死にググることで得たこと

  • ECSではFargateEC2を選ぶことができる
  • FargateEC2クラスタを構築してコンテナを動かすといった管理は不要でコンテナを実行できる
    • 上記のような基礎的なことはこちらに完璧にまとまっています

dev.classmethod.jp

  • ちょうどこのタスクを始めて1週間たった時期の記事で、タイムリーでとても助かりました!
  • コンテナでJMeterを動かす、という視点が重要だってこと
    • Dockefileを用意しないといけない
    • Dockerfileの読み書きくらいはできるようにならないといけない

ではコンテナでJMeterを動かさなくては

  • DockerfileはDockerHubから拝借します
    • 参考にさせてもらったのは以下

hub.docker.com

  • とくにENTRYPOINT ["/entrypoint.sh"]理解できるまでとても難しくて、この参考例の場合はあくまで負荷検証ツールとしてリソースをどこまで割り当ててJMeterを起動させるかを考えた上でのENTRYPONTになっていた
    • 自分がやりたいのは、単純にシナリオが1回動けばいいだけ。
    • これは、スケジュールが来たらシナリオ監視が1回起動して、シナリオ監視を終えたら停止するコンテナを作ればいいんだという発想になれたことで、先に進めました。バッチ処理みたいにコンテナの利用方法をすれば良いんだなということ。

JMeterでのシナリオ監視結果をCloudWatchに連携しないといけない

  • そしてJMeterでの監視結果をCloudWatchに連携しないといけない
    • これはFargateのログは標準出力に吐き出されたものをCloudWatchと連携できるということなので、シナリオ監視の結果を書き出すことで対応
  • 参考は以下。めちゃくちゃ参考になりました!(もちろんGoogle翻訳した)

www.concurrencylabs.com

ということで参考までに以下の成果物を

FROM alpine:3.9

ARG JMETER_VERSION="5.1.1"
ARG PLUGIN_VERSION="1.4.0" 
ENV JMETER_HOME /opt/apache-jmeter-${JMETER_VERSION}
ENV JMETER_BIN  ${JMETER_HOME}/bin
ENV JMETER_DOWNLOAD_URL  https://archive.apache.org/dist/jmeter/binaries/apache-jmeter-${JMETER_VERSION}.tgz
ENV PLUGIN_DOWNLOAD_URL  https://jmeter-plugins.org/downloads/file/JMeterPlugins-Standard-${PLUGIN_VERSION}.zip
ENV SCENARIO_DOWNLOAD_URL [S3に配置したJMeterのシナリオファイル.jmx]

# Install extra packages
ARG TZ="Asia/Tokyo"
RUN    apk update \
    && apk upgrade \
    && apk add ca-certificates \
    && update-ca-certificates \
    && apk add --update openjdk8-jre tzdata curl unzip bash \
    && apk add --no-cache nss \
    && rm -rf /var/cache/apk/* \
    && mkdir -p /tmp/dependencies  \
    && curl -L --silent ${JMETER_DOWNLOAD_URL} >  /tmp/dependencies/apache-jmeter-${JMETER_VERSION}.tgz  \
    && curl -L --silent ${PLUGIN_DOWNLOAD_URL} >  /tmp/dependencies/JMeterPlugins-${PLUGIN_VERSION}.zip  \
    && curl -L --silent ${SCENARIO_DOWNLOAD_URL} >  /tmp/test-plan.jmx  \
    && mkdir -p /opt  \
    && tar -xzf /tmp/dependencies/apache-jmeter-${JMETER_VERSION}.tgz -C /opt  \
    && unzip -oq "/tmp/dependencies/JMeterPlugins-${PLUGIN_VERSION}.zip" -d $JMETER_HOME  \
    && rm -rf /tmp/dependencies  

ENV PATH $PATH:$JMETER_BIN

WORKDIR ${JMETER_HOME}

ENTRYPOINT [ "jmeter.sh" ]
CMD [ "-n" , "-t" , "/tmp/test-plan.jmx" ] 
  • FargateのCFn
AWSTemplateFormatVersion: "2010-09-09"
Description:
  jmeter-fargate-yml
 
# ------------------------------------------------------------#
# Metadata
# ------------------------------------------------------------# 
Metadata:
  "AWS::CloudFormation::Interface":
    ParameterGroups:
      - Label:
          default: "Project Name Prefix"
        Parameters:
          - ProjectName
      - Label:
          default: "Fargate for ECS Configuration"
        Parameters:
          - ECSClusterName
          - ECSTaskName
          - ECSTaskCPUUnit
          - ECSTaskMemory
          - ECSContainerName
          - ECSImageName
          - ECSServiceName
          - ECSTaskScheduleEventsName
          - ECSTaskDesiredCount
      - Label:
          default: "Netowork Configuration"
        Parameters:
          - VpcId
          - ECSSecurityGroupId
          - ECSSubnetId

    ParameterLabels:
      ECSClusterName:
        default: "ECSClusterName"
      ECSTaskName:
        default: "ECSTaskName"
      ECSTaskCPUUnit:
        default: "ECSTaskCPUUnit"
      ECSTaskMemory:
        default: "ECSTaskMemory"
      ECSContainerName:
        default: "ECSContainerName"
      ECSImageName:
        default: "ECSImageName"
      ECSServiceName:
        default: "ECSServiceName"
      ECSTaskScheduleEventsName:
        default: "ECSTaskScheduleEventsName"
      ECSTaskDesiredCount:
        default: "ECSTaskDesiredCount"
 
# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------# 
Parameters:
  ProjectName:
    Default: jmeter-test
    Type: String
 
#VPCID
  VpcId:
    Description : "VPC ID"
    Type: AWS::EC2::VPC::Id
 
#ECSSecurity Group
  ECSSecurityGroupId:
    Type: AWS::EC2::SecurityGroup::Id
 
#ECSSubnet
  ECSSubnetId:
    Description : "ECS Subnet"
    Type : AWS::EC2::Subnet::Id
 
#ECSClusterName
  ECSClusterName:
    Type: String
    Default: "cluster"
 
#ECSTaskName
  ECSTaskName:
    Type: String
    Default: "task"
 
#ECSTaskCPUUnit
  ECSTaskCPUUnit:
    AllowedValues: [ 256, 512, 1024, 2048, 4096  ]
    Type: String
    Default: "256"
 
#ECSTaskMemory
  ECSTaskMemory:
    AllowedValues: [ 512, 1024, 2048, 4096  ]
    Type: String
    Default: "512"
 
#ECSContainerName
  ECSContainerName:
    Type: String
    Default: "container"
 
#ECSImageName
  ECSImageName:
    Type: String
    Default: "ECRのURI"
 
#ECSServiceName
  ECSServiceName:
    Type: String
    Default: "service"

#ECSTaskScheduleEventsName
  ECSTaskScheduleEventsName:
    Type: String
    Default: "TaskScheduleEvents"

#ECSTaskSchedule-Expression
  ScheduleExpression:
    Type: String
    Description: Cron settings
    Default: 'cron(0 * * * ? *)' #rate(1 hour)
 
#ECSTaskDesiredCount
  ECSTaskDesiredCount:
    Type: Number
    Default: 0

Resources:

# ------------------------------------------------------------#
# ECS Cluster
# ------------------------------------------------------------#
  ECSCluster:
    Type: "AWS::ECS::Cluster"
    Properties:
      ClusterName: !Sub "${ProjectName}-${ECSClusterName}"
 
# ------------------------------------------------------------#
#  ECS LogGroup
# ------------------------------------------------------------#
  ECSLogGroup:
    Type: "AWS::Logs::LogGroup"
    Properties:
      LogGroupName: !Sub "/ecs/logs/${ProjectName}-ecs-group"
 
# ------------------------------------------------------------#
#  ECS Task Execution Role
# ------------------------------------------------------------#
  ECSTaskExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub "${ProjectName}-ECSTaskExecutionRolePolicy"
      Path: /
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
 
# ------------------------------------------------------------#
#  ECS TaskDefinition
# ------------------------------------------------------------#
  ECSTaskDefinition:
    Type: "AWS::ECS::TaskDefinition"
    Properties:
      Cpu: !Ref ECSTaskCPUUnit
      ExecutionRoleArn: !Ref ECSTaskExecutionRole
      Family: !Sub "${ProjectName}-${ECSTaskName}"
      Memory: !Ref ECSTaskMemory
      NetworkMode: awsvpc
      RequiresCompatibilities:
        - FARGATE
 
# ------------------------------------------------------------#
#ContainerDefinitions
# ------------------------------------------------------------#
      ContainerDefinitions:
        - Name: !Sub "${ProjectName}-${ECSContainerName}"
          Image: !Ref ECSImageName 
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref ECSLogGroup
              awslogs-region: !Ref "AWS::Region"
              awslogs-stream-prefix: !Ref ProjectName
          MemoryReservation: 128
          PortMappings:
            - HostPort: 4445
              Protocol: tcp
              ContainerPort: 4445
 
# ------------------------------------------------------------#
#  ECS Service
# ------------------------------------------------------------#
  ECSService:
    Type: AWS::ECS::Service
    Properties:
      Cluster: !Ref ECSCluster
      DesiredCount: !Ref ECSTaskDesiredCount
      LaunchType: FARGATE
      NetworkConfiguration:
       AwsvpcConfiguration:
           AssignPublicIp: ENABLED 
           SecurityGroups:
             - !Ref ECSSecurityGroupId
           Subnets:
             - !Ref ECSSubnetId
      ServiceName: !Sub "${ProjectName}-${ECSServiceName}"
      TaskDefinition: !Ref ECSTaskDefinition

# ------------------------------------------------------------#
#  ECS TaskSchedule # ここ以下をCFnで指定するとエラーになる
# ------------------------------------------------------------#
  # TaskScheduleEvents:
    # Type: AWS::Events::Rule
    # Properties:
      # Name: !Ref ECSTaskScheduleEventsName
      # ScheduleExpression: !Ref ScheduleExpression
      # State: ENABLED
      # Targets:
        # - Id: 1
          # Arn: !GetAtt ECSCluster.Arn
          # RoleArn: !GetAtt ECSTaskExecutionRole.Arn
          # EcsParameters:
            # TaskDefinitionArn: !Ref ECSTaskDefinition
            # TaskCount: 1
            # LaunchType: FARGATE
            # NetworkConfiguration:
            #   AwsvpcConfiguration:
            #     AssignPublicIp: ENABLED #本番はdisable
            #     SecurityGroups:
            #       - !Ref ECSSecurityGroupId
            #     Subnets:
            #       - !Ref ECSSubnetId
      # TaskSchedulerRole:
      #   Type: AWS::IAM::Role
      #   Properties:
      #     AssumeRolePolicyDocument:
      #       Version: "2012-10-17"
      #       Statement:
      #         -
      #           Effect: "Allow"
      #           Principal:
      #             Service:
      #               - "events.amazonaws.com"
      #           Action:
      #             - "sts:AssumeRole"
      #     Path: /
      #     Policies:
      #       - PolicyDocument:
      #           Statement:
      #             - Effect: "Allow"
      #               Condition:
      #                 ArnEquals:
      #                   ecs:cluster: !Ref ECSClusterArn
      #               Action: "ecs:RunTask"
      #               Resource: "*"
      #             - Effect: "Allow"
      #               Condition:
      #                 ArnEquals:
      #                   ecs:cluster: !Ref ECSClusterArn
      #               Action:
      #                 - "iam:ListInstanceProfiles"
      #                 - "iam:ListRoles"
      #                 - "iam:PassRole"
      #               Resource: "*"
      #         PolicyName: "TaskSchedulerPolicy"

注意点

  • で、上記のファイルを利用するにあたっての注意点が2つ
    • Fargateでスケジュール起動のタスクを作成しようよしても失敗する

qiita.com

  • ECRにDockerfileを登録しておく必要がある

把握しないといけないこと多い、、、

  • FargateでJMeter構築ってだけかと思ったら、色々と付随して把握しないといけないことがでてきて大変。でも今はこうして1つ1つクリアしていくことが楽しいです!
  • あとFargateは元になるDockerfileがポイントだなって感じました。ここでセンスの良し悪しが決まると思いました。