20200205のAWSに関する記事は10件です。

「セキュアで堅牢なAWSアカウント」を実現する CloudFormationテンプレート - ④アクセスキーのローテーションと削除

はじめに

AWSには、アカウントやリソースへの脅威検知に対応した、AWS IAM Access Analyzer, AWS Security Hub, Amazon Inspector, Amazon GuardDuty, AWS CloudTrail, AWS Config などのサービスが用意されています。

また、CIS AWS Foundations Benchmark というセキュリティガイドラインが公開されており、このガイドラインは、AWSアカウントをセキュアに保つために必要なAWSのセキュリティ設定を集めたベストプラクティス集として活用できます。自身のAWSアカウントがこのガイドラインにどの程度準拠しているのかを確認/監査する手段として、AWS Security Hubで、CIS AWS Foundations Standardという機能が提供されています。

本記事では、アカウントやリソースへの脅威検知が可能なAWSサービスを有効化するとともに、CIS AWS Foundations Benchmarkに限りなく準拠することで、セキュアで堅牢なAWSアカウントを実現します。また、これらをお手軽に実現できるCloudFormationテンプレートを公開しています。

このCloudFormationテンプレートの実行はこちらから。

cloudformation-launch-stack

アクセスキーのローテーション

アクセスキー とは、IAM ユーザーまたは ルートユーザー の 長期的な認証情報を指し、AWS SDK や AWS API を実行する際にこのアクセスキーを指定することで、このアクセスキーに許可された権限を実行することができます。したがって、このアクセスキーが外部に流出した場合には、 第三者がアカウントへの永続的アクセスを取得する可能性 があります。そこで、CIS AWS Foundations Benchmark では、

  • 1.3 90 日間以上使用されていない認証情報は無効にします
  • 1.4 アクセスキーは 90 日ごとに更新します

というガイドラインを規定しており、アクセスキーの定期的なローテーションによって、意図せぬアクセスのリスクを低減するように求めています。

上記ガイドラインに準拠するためには、AWS Config を用いて、定期的に現在の状態の確認 を行う必要があります。また、上記基準を満たしていないことが判明した場合は、当該のアクセスキーを無効化、もしくは削除 する必要があります。

AWS Config

AWS Config を設定して、上記ガイドラインに準拠しているかどうかを定期的に確認します。「セキュアで堅牢なAWSアカウント」を実現する CloudFormationテンプレート - ①サービスの有効化も合わせてご覧ください。

サービスにリンクされたロールの作成

AWS Config で使用する Service-Linked Role を作成します。この Service-Linked Role は、 AWSリソースへの読み込み権限S3への書き込み権限Config に、また、IAMとSystemManagerへの書き込み権限Config Remediation にそれぞれ付与します。

Resources:
  ServiceLinkedRoleForConfig:
    Type: AWS::IAM::ServiceLinkedRole
    DeletionPolicy: Retain
    Properties: 
      AWSServiceName: config.amazonaws.com
      Description: A service-linked role required for AWS Config to access your resources.
  ServiceLinkedRoleForConfigRemediation:
    Type: AWS::IAM::ServiceLinkedRole
    DeletionPolicy: Retain
    Properties: 
      AWSServiceName: remediation.config.amazonaws.com 
      Description: A service-linked role required for AWS Config Remediation to access your resources.

AWS Configの有効化

AWS ConfigDeliveryChannelConfigurationRecorder を作成します。

Resources:
  ConfigDeliveryChannel:
    Type: AWS::Config::DeliveryChannel
    Properties:
      Name: default
      S3BucketName: !Ref S3ForConfig
      SnsTopicARN: !Ref SnsTopicARN
  ConfigConfigurationRecorder:
    Type: AWS::Config::ConfigurationRecorder
    Properties:
      Name: default
      RecordingGroup:
        AllSupported: true
        IncludeGlobalResourceTypes: true
      RoleARN: !Sub arn:aws:iam::DefaultSecuritySettings:role/aws-service-role/config.amazonaws.com/AWSServiceRoleForConfig

Amazon S3 バケットを作成

設定情報 (履歴ファイルやスナップショット) を保存するために使用する、Amazon S3 バケットと、それに紐づくバケットポリシーを作成します。

Resources:
  S3ForConfig:
    Type: 'AWS::S3::Bucket'
    DeletionPolicy: Retain
    Properties:
      BucketName: !Sub defaultsecuritysettings-config-${AWS::Region}-${AWS::AccountId}
      LifecycleConfiguration:
        Rules:
          - Id: ExpirationInDays
            ExpirationInDays: 60
            Status: Enabled
      PublicAccessBlockConfiguration: 
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
  S3BucketPolicyForConfig:
    Type: AWS::S3::BucketPolicy
    Properties: 
      Bucket: !Ref S3ForConfig
      PolicyDocument:
        Version: 2012-10-17
        Id: !Ref S3ForConfig
        Statement:
          - Effect: Allow
            Principal:
              Service: config.amazonaws.com
            Action:
              - 's3:GetBucketAcl'
              - 's3:ListBucket'
            Resource:
              - !GetAtt S3ForConfig.Arn
          - Effect: Allow
            Principal:
              Service: config.amazonaws.com
            Action:
              - 's3:PutObject'
            Resource:
              - !Join
                - ''
                - - !GetAtt S3ForConfig.Arn
                  - /AWSLogs/
                  - !Sub ${AWS::AccountId}
                  - /Config/*
            Condition:
              StringEquals:
                s3:x-amz-acl: bucket-owner-full-control

AWS Config マネージドルールの追加

AWSによって管理されている AWS Config マネージドルール の中に、 access-keys-rotated という名称で、アクティブなアクセスキーが指定された日数内にローテーションされるかどうかを確認できるルール が用意されているので、これを追加します。

Resources:
  ConfigIamAccessKeysRotated:
    DependsOn:
      - ConfigConfigurationRecorder
    Type: 'AWS::Config::ConfigRule'
    Properties:
      ConfigRuleName: access-keys-rotated
      Description: アクティブなアクセスキーが、maxAccessKeyAge で指定された日数内にローテーションされるかどうかを確認します。
      InputParameters:
        maxAccessKeyAge: 90
      Source:
        Owner: AWS
        SourceIdentifier: ACCESS_KEYS_ROTATED

上記で設定した AWS Config ルールは定期的に実行され、アカウントがこのルールに準拠しているかどうかの判定が行われます。しかし、これらの設定は、ルールに準拠しているかどうかを判定するだけであるため、もしルールに非準拠であることが判明した場合には、該当するアクセスキーを手動で削除する必要があります

アクセスキーの強制削除

そこで、以下の内容も設定に加えることで、ガイドラインの基準に違反するアクセスキーを強制的に削除 することができます。ただし下記の設定は、ユーザに確認を求めることなく問答無用にアクセスキーを削除してしまう ため、下記設定を加えるかどうかについては 慎重な検討が必要 です。またもしこの設定を適用する場合でも、ご自身のアカウントの運用ポリシーに合った内容に処理を書き換えることをオススメします

Amazon CloudWatch Eventsの追加

AWS Config が発行する Amazon CloudWatch Eventsのうち、AWS Configに非準拠のルールが検知された場合のみを抽出して、後述の アクセスキーを強制削除するLambdaの実行トリガ にします。

  CloudWatchEventsForConfigIamAccessKeysRotated:
    Type: 'AWS::Events::Rule'
    Properties: 
      Description: CloudWatch Events about Config when IAM Access Keys are rotated.
      EventPattern:
        source:
          - aws.config
        detail-type: 
          - Config Rules Compliance Change
        detail:
          messageType:
            - ComplianceChangeNotification
          newEvaluationResult:
            complianceType:
              - NON_COMPLIANT
      Name: Config
      State: ENABLED
      Targets:
        - Arn: !GetAtt LambdaDeleteExpiredAccessKeys.Arn
          Id: lambda

Lambda 関数の追加

アクセスキーを強制削除するLambdaを作成します。このLambdaは、AWS Configから通知された内容が、 アクティブなアクセスキーが指定された日数(=90日)内にローテーションされるかどうかを確認できる、access-keys-rotated に関連するものであることを確認した上で、作成から90日が経過したアクセスキーのみを削除 します。

  LambdaDeleteExpiredAccessKeys:
    Type: 'AWS::Lambda::Function'
    Properties:
      Code:
        ZipFile: |
          import boto3
          import datetime
          import time
          import logging
          logger = logging.getLogger()
          logger.setLevel(logging.INFO)
          def lambda_handler(event, context):
              logger.info(str(event))

              if 'detail' in event:
                  detail = event['detail']
                  if 'configRuleName' in detail:
                      # access-keys-rotated
                      if detail['configRuleName'] == 'access-keys-rotated':
                          iam = boto3.client('iam')
                          users = iam.list_users()
                          for user in users['Users']:
                              if detail['resourceId'] == user['UserId']:
                                  access_keys = iam.list_access_keys(
                                      UserName=user['UserName']
                                      )
                                  for access_key in access_keys['AccessKeyMetadata']:
                                      create_date = access_key['CreateDate'].timestamp()
                                      now = time.time()
                                      if now - create_date > 60*60*24*90:
                                          response = iam.delete_access_key(
                                              UserName=user['UserName'],
                                              AccessKeyId=access_key['AccessKeyId']
                                          )
      Description: 有効期限が過ぎたアクセスキーを削除します
      FunctionName: deleteExpiredAccessKeys
      Handler: index.lambda_handler
      MemorySize: 128
      Role: !GetAtt IAMRoleForLambda.Arn
      Runtime: python3.7
      Tags:
        - Key: !Ref TagKey
          Value: !Ref TagValue
      Timeout: 3
      TracingConfig:
        Mode: Active
  LambdaDeleteExpiredAccessKeysPermission:
    Type: 'AWS::Lambda::Permission'
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !Ref LambdaDeleteExpiredAccessKeys
      Principal: events.amazonaws.com
      SourceArn: !GetAtt CloudWatchEventsForConfigIamAccessKeysRotated.Arn
  LambdaDeleteExpiredAccessKeysLogGroup:
    Type: AWS::Logs::LogGroup
    Properties: 
      LogGroupName: !Sub /aws/lambda/${LambdaDeleteExpiredAccessKeys}
      RetentionInDays: 60

下記のCloudFormationテンプレートを実行することで、上記設定を含む、 AWS IAM Access Analyzer, AWS Security Hub, Amazon Inspector, Amazon GuardDuty, AWS CloudTrail, AWS Config などのサービス全てを有効化することも、今回の設定を含むAWS Configのみを有効化することも可能です。

作成されるAWSサービス CloudFormationテンプレート
全てのセキュリティサービス cloudformation-launch-stack
AWS Configのみ cloudformation-launch-stack

関連リンク

  1. サービスの有効化 - 「セキュアで堅牢なAWSアカウント」を実現する CloudFormationテンプレート
  2. パスワードポリシーの自動修復 - 「セキュアで堅牢なAWSアカウント」を実現する CloudFormationテンプレート
  3. モニタリングと通知の設定 - 「セキュアで堅牢なAWSアカウント」を実現する CloudFormationテンプレート
  4. アクセスキーのローテーションと削除 - 「セキュアで堅牢なAWSアカウント」を実現する CloudFormationテンプレート
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

「セキュアで堅牢なAWSアカウント」を実現する CloudFormationテンプレート - ③CISに準拠するためのモニタリングと通知の設定

はじめに

AWSには、アカウントやリソースへの脅威検知に対応した、AWS IAM Access Analyzer, AWS Security Hub, Amazon Inspector, Amazon GuardDuty, AWS CloudTrail, AWS Config などのサービスが用意されています。

また、CIS AWS Foundations Benchmark というセキュリティガイドラインが公開されており、このガイドラインは、AWSアカウントをセキュアに保つために必要なAWSのセキュリティ設定を集めたベストプラクティス集として活用できます。自身のAWSアカウントがこのガイドラインにどの程度準拠しているのかを確認/監査する手段として、AWS Security Hubで、CIS AWS Foundations Standardという機能が提供されています。

本記事では、アカウントやリソースへの脅威検知が可能なAWSサービスを有効化するとともに、CIS AWS Foundations Benchmarkに限りなく準拠することで、セキュアで堅牢なAWSアカウントを実現します。また、これらをお手軽に実現できるCloudFormationテンプレートを公開しています。

このCloudFormationテンプレートの実行はこちらから。

cloudformation-launch-stack

モニタリングと通知の設定

CIS AWS Foundations Benchmark の第3章には、AWSリソースの モニタリング とその 通知 を定めた、以下のガイドライン項目が存在します。

No. ルール
3.1 不正な API 呼び出し に対してログメトリクスフィルタとアラームが存在することを確認します
3.2 MFA なし の AWS マネジメントコンソール サインイン に対してログメトリクスフィルタとアラームが存在することを確認します
3.3 「ルート」アカウントに対してログメトリクスフィルタとアラームが存在することを確認します
3.4 MFA なしIAM ポリシーの変更 に対してログメトリクスフィルタとアラームが存在することを確認します
3.5 MFA なしCloudTrail 設定の変更 に対してログメトリクスフィルタとアラームが存在することを確認します
3.6 AWS マネジメントコンソール 認証の失敗 に対してログメトリクスフィルタとアラームが存在することを確認します
3.7 カスタマー作成の CMK の無効化またはスケジュールされた削除 に対してログメトリクスフィルタとアラームが存在することを確認します
3.8 S3 バケットの変更 に対してログメトリクスフィルタとアラームが存在することを確認します
3.9 AWS Config 設定の変更 に対してログメトリクスフィルタとアラームが存在することを確認します
3.10 セキュリティグループの変更 に対するメトリクスフィルタとアラームが存在することを確認します
3.11 ネットワークアクセスコントロールリスト (NACL) への変更 に対するログメトリクスとアラームが存在することを確認します
3.12 ネットワークゲートウェイへの変更 に対するログメトリクスとアラームが存在することを確認します
3.13 ルートテーブルの変更 に対してログメトリクスフィルタとアラームが存在することを確認します
3.14 VPC の変更 に対してログメトリクスフィルタとアラームが存在することを確認します

これらのガイドラインに準拠するためには、 まず CloudTrailを有効化 した上で、このログを CloudWatch Logs上に出力 します。また、Amazon CloudWatch MetricFilter を用いてログの中から上記項目に該当するデータのみを抽出して メトリクスを作成 します。 メトリクスのデータが Amazon CloudWatch Alarm で設定した閾値を超えると、Amazon SNS を経由してその情報がユーザに通知されます。

AWS CloudTrail

CloudTrail を有効化して、このログを CloudWatch Logs に出力します。「セキュアで堅牢なAWSアカウント」を実現する CloudFormationテンプレート - ①サービスの有効化も合わせてご覧ください。

IAMロールの作成

AWS CloudTrail で使用する IAM Roleを作成します。

Resources:
  IAMRoleForCloudTrail:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service: cloudtrail.amazonaws.com
            Action: 'sts:AssumeRole'
      Description: A role required for CloudTrail to access CloudWatch Logs.
      Policies:
        - PolicyName: !Sub 'DefaultSecuritySettings-AWSCloudTrailCloudWatchLogsPolicy-${AWS::Region}'
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - 'logs:PutLogEvents'
                  - 'logs:CreateLogStream'
                Resource:
                  - !GetAtt CloudWatchLogsGroupForCloudTrail.Arn
      RoleName: !Sub 'DefaultSecuritySettings-CloudTrail-${AWS::Region}'

AWS CloudTrail を有効化

AWS CloudTrailを有効化します。

Resources:
  CloudTrail:
    DependsOn:
      - S3BucketPolicyForCloudTrail
    Condition: CreateCentralizedResources
    Type: AWS::CloudTrail::Trail
    Properties:
      CloudWatchLogsLogGroupArn: !GetAtt CloudWatchLogsGroupForCloudTrail.Arn
      CloudWatchLogsRoleArn: !GetAtt IAMRoleForCloudTrail.Arn
      EnableLogFileValidation: true
      EventSelectors:
        - DataResources: 
            # All S3 buckets
            - Type: AWS::S3::Object
              Values: 
                - arn:aws:s3
            # All Lambda functions.
            - Type: AWS::Lambda::Function
              Values: 
                - arn:aws:lambda
      IncludeGlobalServiceEvents: true
      IsLogging: true
      IsMultiRegionTrail: true
      SnsTopicName: !Ref SnsTopicName
      TrailName: DefaultSecuritySettings

CloudWatch Logs のロググループを作成

イベントログファイルを蓄積するために使用する、ロググループを作成します。

Resources:
  CloudWatchLogsGroupForCloudTrail:
    Type: 'AWS::Logs::LogGroup'
    Properties:
      LogGroupName: /aws/cloudtrail/DefaultSecuritySettings
      RetentionInDays: 365

Amazon CloudWatch

CloudTrailのログから Amazon CloudWatch MetricFilter を用いて該当するデータのみを抽出し、 メトリクスを作成します。 メトリクスのデータが Amazon CloudWatch Alarm で設定した閾値を超えると、Amazon SNS を経由してその情報がユーザに通知されます。

3.1 不正な API 呼び出しに対してログメトリクスフィルタとアラームが存在することを確認します

Resources: 
  CloudWatchLogsMetricFilterCloudTrailUnauthorizedAPICalls:
    Type: 'AWS::Logs::MetricFilter'
    Properties:
      FilterPattern: '{($.errorCode="*UnauthorizedOperation") || ($.errorCode="AccessDenied*")}'
      LogGroupName: !Ref CloudWatchLogsGroupForCloudTrail
      MetricTransformations:
        - MetricName: UnauthorizedAPICalls 
          MetricNamespace: LogMetrics
          MetricValue: '1'
  CloudWatchAlarmMetricFilterCloudTrailUnauthorizedAPICalls:
    Type: 'AWS::CloudWatch::Alarm'
    Properties:
      ActionsEnabled: true
      AlarmActions:
        - !Ref SnsTopicARN
      AlarmDescription: '*CloudTrail*  *不正なAPIコールを検知* しました。'
      AlarmName: !Sub 'Warning-${AWS::StackName}-CloudTrail-Unauthorized-API-Calls'
      ComparisonOperator: GreaterThanOrEqualToThreshold
      DatapointsToAlarm: 6
      EvaluationPeriods: 60
      MetricName: UnauthorizedAPICalls
      Namespace: LogMetrics
      OKActions:
        - !Ref SnsTopicARN
      Period: 60
      Statistic: Maximum
      Threshold: 1
      TreatMissingData: notBreaching

3.2 MFA なしのAWS マネジメントコンソールサインインに対してログメトリクスフィルタとアラームが存在することを確認します

Resources:
  CloudWatchLogsMetricFilterCloudTrailLoginWithoutMFA:
    Type: 'AWS::Logs::MetricFilter'
    Properties:
      FilterPattern: '{($.eventName="ConsoleLogin") && ($.additionalEventData.MFAUsed !="Yes")}'
      LogGroupName: !Ref CloudWatchLogsGroupForCloudTrail
      MetricTransformations:
        - MetricName: LoginWithoutMFA 
          MetricNamespace: LogMetrics
          MetricValue: '1'
  CloudWatchAlarmMetricFilterCloudTrailLoginWithoutMFA:
    Type: 'AWS::CloudWatch::Alarm'
    Properties:
      ActionsEnabled: true
      AlarmActions:
        - !Ref SnsTopicARN
      AlarmDescription: '*CloudTrail*  *MFAなしのログインを検知* しました。'
      AlarmName: !Sub 'Warning-${AWS::StackName}-CloudTrail-Login-Without-MFA'
      ComparisonOperator: GreaterThanOrEqualToThreshold
      EvaluationPeriods: 1
      MetricName: LoginWithoutMFA
      Namespace: LogMetrics
      OKActions:
        - !Ref SnsTopicARN
      Period: 60
      Statistic: Maximum
      Threshold: 1
      TreatMissingData: notBreaching

3.3 「ルート」アカウントに対してログメトリクスフィルタとアラームが存在することを確認します

Resources:
  CloudWatchLogsMetricFilterCloudTrailForRootAcount:
    Type: 'AWS::Logs::MetricFilter'
    Properties:
      FilterPattern: '{$.userIdentity.type="Root" && $.userIdentity.invokedBy NOT EXISTS && $.eventType !="AwsServiceEvent"}'
      LogGroupName: !Ref CloudWatchLogsGroupForCloudTrail
      MetricTransformations:
        - MetricName: ForRootAcount 
          MetricNamespace: LogMetrics
          MetricValue: '1'
  CloudWatchAlarmMetricFilterCloudTrailForRootAcount:
    Type: 'AWS::CloudWatch::Alarm'
    Properties:
      ActionsEnabled: true
      AlarmActions:
        - !Ref SnsTopicARN
      AlarmDescription: '*CloudTrail*  *ルートアカウントに対する変更を検知* しました。'
      AlarmName: !Sub 'Warning-${AWS::StackName}-CloudTrail-For-RootAcount'
      ComparisonOperator: GreaterThanOrEqualToThreshold
      EvaluationPeriods: 1
      MetricName: ForRootAcount
      Namespace: LogMetrics
      OKActions:
        - !Ref SnsTopicARN
      Period: 60
      Statistic: Maximum
      Threshold: 1
      TreatMissingData: notBreaching

3.4 IAM ポリシーの変更に対してログメトリクスフィルタとアラームが存在することを確認します

Resources:
  CloudWatchLogsMetricFilterCloudTrailIamPolicyChanges:
    Type: 'AWS::Logs::MetricFilter'
    Properties:
      FilterPattern: '{($.eventName=DeleteGroupPolicy) || ($.eventName=DeleteRolePolicy) || ($.eventName=DeleteUserPolicy) || ($.eventName=PutGroupPolicy) || ($.eventName=PutRolePolicy) || ($.eventName=PutUserPolicy) || ($.eventName=CreatePolicy) || ($.eventName=DeletePolicy) || ($.eventName=CreatePolicyVersion) || ($.eventName=DeletePolicyVersion) || ($.eventName=AttachRolePolicy) || ($.eventName=DetachRolePolicy) || ($.eventName=AttachUserPolicy) || ($.eventName=DetachUserPolicy) || ($.eventName=AttachGroupPolicy) || ($.eventName=DetachGroupPolicy)}'
      LogGroupName: !Ref CloudWatchLogsGroupForCloudTrail
      MetricTransformations:
        - MetricName: IamPolicyChanges 
          MetricNamespace: LogMetrics
          MetricValue: '1'
  CloudWatchAlarmMetricFilterCloudTrailIamPolicyChanges:
    Type: 'AWS::CloudWatch::Alarm'
    Properties:
      ActionsEnabled: true
      AlarmActions:
        - !Ref SnsTopicARN
      AlarmDescription: '*CloudTrail*  *IAMポリシーの変更を検知* しました。'
      AlarmName: !Sub 'Warning-${AWS::StackName}-CloudTrail-IamPolicy-Changes'
      ComparisonOperator: GreaterThanOrEqualToThreshold
      EvaluationPeriods: 1
      MetricName: IamPolicyChanges
      Namespace: LogMetrics
      OKActions:
        - !Ref SnsTopicARN
      Period: 60
      Statistic: Maximum
      Threshold: 1
      TreatMissingData: notBreaching

3.5 CloudTrail設定の変更に対してログメトリクスフィルタとアラームが存在することを確認します

Resources: 
  CloudWatchLogsMetricFilterCloudTrailCloudTrailConfigurationChanges:
    Type: 'AWS::Logs::MetricFilter'
    Properties:
      FilterPattern: '{($.eventName=CreateTrail) || ($.eventName=UpdateTrail) || ($.eventName=DeleteTrail) || ($.eventName=StartLogging) || ($.eventName=StopLogging)}'
      LogGroupName: !Ref CloudWatchLogsGroupForCloudTrail
      MetricTransformations:
        - MetricName: CloudTrailConfigurationChanges 
          MetricNamespace: LogMetrics
          MetricValue: '1'
  CloudWatchAlarmMetricFilterCloudTrailCloudTrailConfigurationChanges:
    Type: 'AWS::CloudWatch::Alarm'
    Properties:
      ActionsEnabled: true
      AlarmActions:
        - !Ref SnsTopicARN
      AlarmDescription: '*CloudTrail*  *CloudTrailの設定変更を検知* しました。'
      AlarmName: !Sub 'Warning-${AWS::StackName}-CloudTrail-CloudTrailConfiguration-Changes'
      ComparisonOperator: GreaterThanOrEqualToThreshold
      EvaluationPeriods: 1
      MetricName: CloudTrailConfigurationChanges
      Namespace: LogMetrics
      OKActions:
        - !Ref SnsTopicARN
      Period: 60
      Statistic: Maximum
      Threshold: 1
      TreatMissingData: notBreaching

3.6 AWS マネジメントコンソール 認証の失敗 に対してログメトリクスフィルタとアラームが存在することを確認します

Resources:
  CloudWatchLogsMetricFilterCloudTrailAuthenticationFailuresDetected:
    Type: 'AWS::Logs::MetricFilter'
    Properties:
      FilterPattern: '{($.eventName=ConsoleLogin) && ($.errorMessage="Failed authentication")}'
      LogGroupName: !Ref CloudWatchLogsGroupForCloudTrail
      MetricTransformations:
        - MetricName: AuthenticationFailuresDetected 
          MetricNamespace: LogMetrics
          MetricValue: '1'
  CloudWatchAlarmMetricFilterCloudTrailAuthenticationFailuresDetected:
    Type: 'AWS::CloudWatch::Alarm'
    Properties:
      ActionsEnabled: true
      AlarmActions:
        - !Ref SnsTopicARN
      AlarmDescription: '*CloudTrail*  *ログイン認証失敗を検知* しました。'
      AlarmName: !Sub 'Warning-${AWS::StackName}-CloudTrail-AuthenticationFailures-Detected'
      ComparisonOperator: GreaterThanOrEqualToThreshold
      EvaluationPeriods: 1
      MetricName: AuthenticationFailuresDetected
      Namespace: LogMetrics
      OKActions:
        - !Ref SnsTopicARN
      Period: 60
      Statistic: Maximum
      Threshold: 1
      TreatMissingData: notBreaching

3.7 カスタマー作成のCMK の無効化またはスケジュールされた削除に対してログメトリクスフィルタとアラームが存在することを確認します

Resources:
  CloudWatchLogsMetricFilterCloudTrailCustomerCreatedCMKsChanges:
    Type: 'AWS::Logs::MetricFilter'
    Properties:
      FilterPattern: '{($.eventSource=kms.amazonaws.com) && (($.eventName=DisableKey) || ($.eventName=ScheduleKeyDeletion))}'
      LogGroupName: !Ref CloudWatchLogsGroupForCloudTrail
      MetricTransformations:
        - MetricName: CustomerCreatedCMKsChanges 
          MetricNamespace: LogMetrics
          MetricValue: '1'
  CloudWatchAlarmMetricFilterCloudTrailCustomerCreatedCMKsChanges:
    Type: 'AWS::CloudWatch::Alarm'
    Properties:
      ActionsEnabled: true
      AlarmActions:
        - !Ref SnsTopicARN
      AlarmDescription: '*CloudTrail*  *カスタマー作成CMKの無効化もしくは削除を検知* しました。'
      AlarmName: !Sub 'Warning-${AWS::StackName}-CloudTrail-CustomerCreatedCMKs-Changes'
      ComparisonOperator: GreaterThanOrEqualToThreshold
      EvaluationPeriods: 1
      MetricName: CustomerCreatedCMKsChanges
      Namespace: LogMetrics
      OKActions:
        - !Ref SnsTopicARN
      Period: 60
      Statistic: Maximum
      Threshold: 1
      TreatMissingData: notBreaching

3.8 S3 バケットの変更に対してログメトリクスフィルタとアラームが存在することを確認します

Resources:
  CloudWatchLogsMetricFilterCloudTrailS3BucketPolicyChanges:
    Type: 'AWS::Logs::MetricFilter'
    Properties:
      FilterPattern: '{($.eventSource=s3.amazonaws.com) && (($.eventName=PutBucketAcl) || ($.eventName=PutBucketPolicy) || ($.eventName=PutBucketCors) || ($.eventName=PutBucketLifecycle) || ($.eventName=PutBucketReplication) || ($.eventName=DeleteBucketPolicy) || ($.eventName=DeleteBucketCors) || ($.eventName=DeleteBucketLifecycle) || ($.eventName=DeleteBucketReplication))}'
      LogGroupName: !Ref CloudWatchLogsGroupForCloudTrail
      MetricTransformations:
        - MetricName: S3BucketPolicyChanges 
          MetricNamespace: LogMetrics
          MetricValue: '1'
  CloudWatchAlarmMetricFilterCloudTrailS3BucketPolicyChanges:
    Type: 'AWS::CloudWatch::Alarm'
    Properties:
      ActionsEnabled: true
      AlarmActions:
        - !Ref SnsTopicARN
      AlarmDescription: '*CloudTrail*  *S3のバケットポリシーの変更を検知* しました。'
      AlarmName: !Sub 'Notice-${AWS::StackName}-CloudTrail-S3BucketPolicy-Changes'
      ComparisonOperator: GreaterThanOrEqualToThreshold
      EvaluationPeriods: 1
      MetricName: S3BucketPolicyChanges
      Namespace: LogMetrics
      OKActions:
        - !Ref SnsTopicARN
      Period: 60
      Statistic: Maximum
      Threshold: 1
      TreatMissingData: notBreaching

3.9 AWS Config 設定の変更に対してログメトリクスフィルタとアラームが存在することを確認します

Resources: 
  CloudWatchLogsMetricFilterCloudTrailConfigConfigurationChanges:
    Type: 'AWS::Logs::MetricFilter'
    Properties:
      FilterPattern: '{($.eventSource=config.amazonaws.com) && (($.eventName=StopConfigurationRecorder) || ($.eventName=DeleteDeliveryChannel) || ($.eventName=PutDeliveryChannel) || ($.eventName=PutConfigurationRecorder))}'
      LogGroupName: !Ref CloudWatchLogsGroupForCloudTrail
      MetricTransformations:
        - MetricName: ConfigConfigurationChanges 
          MetricNamespace: LogMetrics
          MetricValue: '1'
  CloudWatchAlarmMetricFilterCloudTrailConfigConfigurationChanges:
    Type: 'AWS::CloudWatch::Alarm'
    Properties:
      ActionsEnabled: true
      AlarmActions:
        - !Ref SnsTopicARN
      AlarmDescription: '*CloudTrail*  *Configの設定変更を検知* しました。'
      AlarmName: !Sub 'Warning-${AWS::StackName}-CloudTrail-ConfigConfiguration-Changes'
      ComparisonOperator: GreaterThanOrEqualToThreshold
      EvaluationPeriods: 1
      MetricName: ConfigConfigurationChanges
      Namespace: LogMetrics
      OKActions:
        - !Ref SnsTopicARN
      Period: 60
      Statistic: Maximum
      Threshold: 1
      TreatMissingData: notBreaching

3.10 セキュリティグループの変更に対するメトリクスフィルタとアラームが存在することを確認します

Resources: 
  CloudWatchLogsMetricFilterCloudTrailSecurityGroupChanges:
    Type: 'AWS::Logs::MetricFilter'
    Properties:
      FilterPattern: '{($.eventName=AuthorizeSecurityGroupIngress) || ($.eventName=AuthorizeSecurityGroupEgress) || ($.eventName=RevokeSecurityGroupIngress) || ($.eventName=RevokeSecurityGroupEgress) || ($.eventName=CreateSecurityGroup) || ($.eventName=DeleteSecurityGroup)}'
      LogGroupName: !Ref CloudWatchLogsGroupForCloudTrail
      MetricTransformations:
        - MetricName: SecurityGroupChanges 
          MetricNamespace: LogMetrics
          MetricValue: '1'
  CloudWatchAlarmMetricFilterCloudTrailSecurityGroupChanges:
    Type: 'AWS::CloudWatch::Alarm'
    Properties:
      ActionsEnabled: true
      AlarmActions:
        - !Ref SnsTopicARN
      AlarmDescription: '*CloudTrail*  *セキュリティグループの変更を検知* しました。'
      AlarmName: !Sub 'Warning-${AWS::StackName}-CloudTrail-SecurityGroup-Changes'
      ComparisonOperator: GreaterThanOrEqualToThreshold
      EvaluationPeriods: 1
      MetricName: SecurityGroupChanges
      Namespace: LogMetrics
      OKActions:
        - !Ref SnsTopicARN
      Period: 60
      Statistic: Maximum
      Threshold: 1
      TreatMissingData: notBreaching

3.11 ネットワークアクセスコントロールリスト (NACL) への変更に対するログメトリクスとアラームが存在することを確認します

Resources:
  CloudWatchLogsMetricFilterCloudTrailNACLChanges:
    Type: 'AWS::Logs::MetricFilter'
    Properties:
      FilterPattern: '{($.eventName=CreateNetworkAcl) || ($.eventName=CreateNetworkAclEntry) || ($.eventName=DeleteNetworkAcl) || ($.eventName=DeleteNetworkAclEntry) || ($.eventName=ReplaceNetworkAclEntry) || ($.eventName=ReplaceNetworkAclAssociation)}'
      LogGroupName: !Ref CloudWatchLogsGroupForCloudTrail
      MetricTransformations:
        - MetricName: NACLChanges 
          MetricNamespace: LogMetrics
          MetricValue: '1'
  CloudWatchAlarmMetricFilterCloudTrailNACLChanges:
    Type: 'AWS::CloudWatch::Alarm'
    Properties:
      ActionsEnabled: true
      AlarmActions:
        - !Ref SnsTopicARN
      AlarmDescription: '*CloudTrail*  *NACLの変更を検知* しました。'
      AlarmName: !Sub 'Notice-${AWS::StackName}-CloudTrail-NACL-Changes'
      ComparisonOperator: GreaterThanOrEqualToThreshold
      EvaluationPeriods: 1
      MetricName: NACLChanges
      Namespace: LogMetrics
      OKActions:
        - !Ref SnsTopicARN
      Period: 60
      Statistic: Maximum
      Threshold: 1
      TreatMissingData: notBreaching

3.12 ネットワークゲートウェイへの変更に対するログメトリクスとアラームが存在することを確認します

Resources:
  CloudWatchLogsMetricFilterCloudTrailNetworkGatewayChanges:
    Type: 'AWS::Logs::MetricFilter'
    Properties:
      FilterPattern: '{($.eventName=CreateCustomerGateway) || ($.eventName=DeleteCustomerGateway) || ($.eventName=AttachInternetGateway) || ($.eventName=CreateInternetGateway) || ($.eventName=DeleteInternetGateway) || ($.eventName=DetachInternetGateway)}'
      LogGroupName: !Ref CloudWatchLogsGroupForCloudTrail
      MetricTransformations:
        - MetricName: NetworkGatewayChanges 
          MetricNamespace: LogMetrics
          MetricValue: '1'
  CloudWatchAlarmMetricFilterCloudTrailNetworkGatewayChanges:
    Type: 'AWS::CloudWatch::Alarm'
    Properties:
      ActionsEnabled: true
      AlarmActions:
        - !Ref SnsTopicARN
      AlarmDescription: '*CloudTrail*  *ネットワークゲートウェイの変更を検知* しました。'
      AlarmName: !Sub 'Notice-${AWS::StackName}-CloudTrail-NetworkGateway-Changes'
      ComparisonOperator: GreaterThanOrEqualToThreshold
      EvaluationPeriods: 1
      MetricName: NetworkGatewayChanges
      Namespace: LogMetrics
      OKActions:
        - !Ref SnsTopicARN
      Period: 60
      Statistic: Maximum
      Threshold: 1
      TreatMissingData: notBreaching

3.13 ルートテーブルの変更に対してログメトリクスフィルタとアラームが存在することを確認します

Resources:
  CloudWatchLogsMetricFilterCloudTrailRouteTableChanges:
    Type: 'AWS::Logs::MetricFilter'
    Properties:
      FilterPattern: '{($.eventName=CreateRoute) || ($.eventName=CreateRouteTable) || ($.eventName=ReplaceRoute) || ($.eventName=ReplaceRouteTableAssociation) || ($.eventName=DeleteRouteTable) || ($.eventName=DeleteRoute) || ($.eventName=DisassociateRouteTable)}'
      LogGroupName: !Ref CloudWatchLogsGroupForCloudTrail
      MetricTransformations:
        - MetricName: RouteTableChanges 
          MetricNamespace: LogMetrics
          MetricValue: '1'
  CloudWatchAlarmMetricFilterCloudTrailRouteTableChanges:
    Type: 'AWS::CloudWatch::Alarm'
    Properties:
      ActionsEnabled: true
      AlarmActions:
        - !Ref SnsTopicARN
      AlarmDescription: '*CloudTrail*  *ルートテーブルの変更を検知* しました。'
      AlarmName: !Sub 'Notice-${AWS::StackName}-CloudTrail-RouteTable-Changes'
      ComparisonOperator: GreaterThanOrEqualToThreshold
      EvaluationPeriods: 1
      MetricName: RouteTableChanges
      Namespace: LogMetrics
      OKActions:
        - !Ref SnsTopicARN
      Period: 60
      Statistic: Maximum
      Threshold: 1
      TreatMissingData: notBreaching

3.14 VPC の変更に対してログメトリクスフィルタとアラームが存在することを確認します

Resources:
  CloudWatchLogsMetricFilterCloudTrailVpcChanges:
    Type: 'AWS::Logs::MetricFilter'
    Properties:
      FilterPattern: '{($.eventName=CreateVpc) || ($.eventName=DeleteVpc) || ($.eventName=ModifyVpcAttribute) || ($.eventName=AcceptVpcPeeringConnection) || ($.eventName=CreateVpcPeeringConnection) || ($.eventName=DeleteVpcPeeringConnection) || ($.eventName=RejectVpcPeeringConnection) || ($.eventName=AttachClassicLinkVpc) || ($.eventName=DetachClassicLinkVpc) || ($.eventName=DisableVpcClassicLink) || ($.eventName=EnableVpcClassicLink)}'
      LogGroupName: !Ref CloudWatchLogsGroupForCloudTrail
      MetricTransformations:
        - MetricName: VpcChanges 
          MetricNamespace: LogMetrics
          MetricValue: '1'
  CloudWatchAlarmMetricFilterCloudTrailVpcChanges:
    Type: 'AWS::CloudWatch::Alarm'
    Properties:
      ActionsEnabled: true
      AlarmActions:
        - !Ref SnsTopicARN
      AlarmDescription: '*CloudTrail*  *VPCの変更を検知* しました。'
      AlarmName: !Sub 'Notice-${AWS::StackName}-CloudTrail-Vpc-Changes'
      ComparisonOperator: GreaterThanOrEqualToThreshold
      EvaluationPeriods: 1
      MetricName: VpcChanges
      Namespace: LogMetrics
      OKActions:
        - !Ref SnsTopicARN
      Period: 60
      Statistic: Maximum
      Threshold: 1
      TreatMissingData: notBreaching

AWS Security Hub を用いて、CIS AWS Foundations Standard に対する達成度を確認することができますが、上記の項目をただ Amazon SNS に送信しただけでは、ガイドライン準拠とは見なされません。ガイドラインに準拠するためには、このAmazon SNSに対する サブスクライバーの登録 が必須となりますので、Eメールや(AWS Chatbotから)Slack等を経由して、上記結果を受信できる環境を整えましょう。

なお、下記のCloudFormationテンプレートを実行することで、上記設定を含む、 AWS IAM Access Analyzer, AWS Security Hub, Amazon Inspector, Amazon GuardDuty, AWS CloudTrail, AWS Config などのサービス全てを有効化することも、今回の設定内容のみを有効化することも可能です。

作成されるAWSサービス CloudFormationテンプレート
全てのセキュリティサービス cloudformation-launch-stack
今回の設定のみ cloudformation-launch-stack

関連リンク

  1. サービスの有効化 - 「セキュアで堅牢なAWSアカウント」を実現する CloudFormationテンプレート
  2. パスワードポリシーの自動修復 - 「セキュアで堅牢なAWSアカウント」を実現する CloudFormationテンプレート
  3. モニタリングと通知の設定 - 「セキュアで堅牢なAWSアカウント」を実現する CloudFormationテンプレート
  4. アクセスキーのローテーションと削除 - 「セキュアで堅牢なAWSアカウント」を実現する CloudFormationテンプレート
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LaravelでAWS認証エラーが起きる?恐らくそれはconfig_cacheの影響です

約6年ぶりくらいにPHP(初Laravel)を触ってるのですがプチハマりしました。残存cacheの影響でハマるとか、物凄く良くありそうなシチュエーションなのに具体的な解決策を見つけられなかったので書いておきます。

LaravelでAWSソリューションを使ってるとこんなエラーレスポンスにめぐりあうかもしれません。

{
  "code": "400",
  "message": "Client error: `GET http://169.254.xxx.xxx/latest/meta-data/iam/security-credentials/` resulted in a `404 Not Found` response:\n<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n\t\"http://www. (truncated...)\n",
  "exception": "GuzzleHttp\\Exception\\ClientException"
}

このエラーはAWSの認証情報であるCredentialsが何かダメですよ!という内容です。
いやいや.envにちゃんとAWS_ACCESS_KEY_IDとAWS_SECRET_ACCESS_KEY書いてあるYO!という時には、恐らくconfigファイルがキャッシュされているせいで正常にAWSのCredentialsが読み込めておらずエラーレスポンスになってる事があります。
ちなみに稼働環境で php artisan config:clear を叩けば最新の.envを読み込み(キャッシュもします)するので解消すると思います。

ただし私の環境ではデプロイにDeployerを使っています。なのでデプロイ時に自動実行してもらう為にtaskへ入れておこうと思ったのですが、どうやら標準のタスクには無いようです。なので自分でdeploy.phpにカスタマイズタスクを記載して呼び出します。

task('artisan:config:clear', function () {
    run('php {{release_path}}/artisan config:clear');
});
/**
 * Main task
 */

desc('Deploy your project');
task('deploy:laravel', [
    'deploy:info',
    'deploy:prepare',
    'deploy:lock',
    'deploy:release',
    'deploy:update_code',
    'deploy:shared',
    'deploy:vendors',
    'deploy:writable',
    'artisan:storage:link',
    'artisan:view:clear',
    'artisan:cache:clear',
    'artisan:config:cache',
    'artisan:optimize',
    // optimizeで何かやってるらしく、ここに書かないとうまく機能しません(面倒なのでちゃんと調べてない)
    'artisan:config:clear',
    'deploy:symlink',
    'deploy:unlock',
    'cleanup',
]);

ぶっちゃけconfig:clearタスクは省いても良い気がするのですがオーバーヘッドも対してなさそうなので放置しています。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS SDKやCLIでS3に対するアカウントレベルの操作を行う場合はS3 Controlを利用する

はじめに

AWS SDKからアカウントレベルでブロックパブリックアクセスの設定を行いたかったのですが
SDKドキュメントのs3の項目にはバケット単位で設定を行う項目はあるものの、
アカウントレベルで設定を行う項目が見当たりません。
アカウントレベルで設定を行うには AWS S3 Control に対してAPIリクエストを行う必要がありました。

AWS S3 Control

AWS S3 Controlは、アカウントレベルのブロックパブリックアクセス設定や
バッチオペレーション、アクセスポイントの管理など、
Amazon S3 コントロールプレーンに対するオペレーションのアクセスを提供します。

提供されているアクションは以下のドキュメントを参照

Amazon S3 API Reference - AWS S3 Control
https://docs.aws.amazon.com/AmazonS3/latest/API/API_Operations_AWS_S3_Control.html

アカウントレベルのブロックパブリックアクセスを設定する場合の例です。

Boto3

以下のように実行します。

import boto3

client = boto3.client('s3control')

response = client.put_public_access_block(
    PublicAccessBlockConfiguration={
        'BlockPublicAcls': True,
        'IgnorePublicAcls': True,
        'BlockPublicPolicy': True,
        'RestrictPublicBuckets': True
    },
    AccountId='123456789012'
)

ドキュメントは以下です。

Boto3 Docs / Available Services / S3Control
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3control.html

AWS CLI

以下のように実行します。

$ aws s3control put-public-access-block --account-id 123456789012 \
--public-access-block-configuration "BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"

コマンドリファレンスは以下です。

AWS CLI Command Reference - S3 Control
https://docs.aws.amazon.com/cli/latest/reference/s3control/index.html

その他の言語の例やリンクについては割愛させていただきます。
以上です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS EC2 AmazonLinux2 のdockerホスト用初期設定

やりたいこと

AmazonLinux2に、以下の設定をまとめて実行する。

  • タイムゾーンを日本に
  • 言語を日本語に
  • git docker docker-compose 最新版インストール
  • 以下のコマンドのエイリアス(ショートカット)作成
元のコマンド エイリアス
docker dcr
docker-compose ddc

追記: dockerのエイリアスを dcr に修正しました:wink:

コマンド

sudo yum update -y
sudo cp /etc/localtime /etc/localtime.org
sudo ln -sf /usr/share/zoneinfo/Japan /etc/localtime
sudo mv /etc/sysconfig/clock /etc/sysconfig/clock.bk
sudo echo -e 'ZONE='Asia/Tokyo'\nUTC=true' > /etc/sysconfig/clock
sudo mv /etc/sysconfig/i18n /etc/sysconfig/i18n.bk
sudo echo 'LANG=ja_JP.UTF-8' > /etc/sysconfig/i18n
export LANG=ja_JP.utf8
export LC_ALL=ja_JP.utf8


sudo yum install -y git jq

sudo amazon-linux-extras install -y docker
sudo service docker start
sudo chkconfig docker on
sudo usermod -a -G docker ec2-user #本番環境などでは危険だからやらないでね!

compose_version=$(curl https://api.github.com/repos/docker/compose/releases/latest | jq .name -r)
output='/usr/local/bin/docker-compose'
sudo curl -L https://github.com/docker/compose/releases/download/$compose_version/docker-compose-$(uname -s)-$(uname -m) -o $output
sudo chmod +x $output

echo "alias dcr='docker'" >> ~/.bashrc
echo "alias ddc='docker-compose'" >> ~/.bashrc

source ~/.bashrc

おまけ: rootでもdocker-composeを使う場合の設定

sudo visudo

sudoers 編集画面を開き

# :/usr/local/binをおしりに追加
Defaults    secure_path = /sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin

の通りに編集して、保存。

そして以下のコマンドを実行。

sudo sh -c "echo alias dcr='docker' >> /root/.bashrc"
sudo sh -c "echo alias ddc='docker-compose' >> /root/.bashrc"
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WindowsにCDK環境構築する手順(Python)

CDK環境(python)を整えるための自分用メモです。
※動作未検証です、pythonのバージョンが違うとエラーになった実績もありますので、参考程度でお願いします。

・AWS CLI導入
https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/install-windows.html#install-msi-on-windows

・AWS IAM アクセスキー設定
コマンドプロンプトで下記コマンド実行
aws configure

※事前にアクセスキーとシークレットキーを用意すること

・Node.js8.0以上導入
https://nodejs.org/ja/

・CDK導入
コマンドプロンプトで下記コマンド実行
npm install -g aws-cdk

・Python3導入
https://www.python.org/downloads/windows/
⇒導入後、「python」コマンドが利用できること(Pathが通っていること)を確認する

・エディタ(VisualStudio Code+Python拡張)
https://code.visualstudio.com

・git導入
https://git-scm.com/download/win

※下記はオプション(とりあえずなくても良い)

・pyyaml導入(設定ファイルをYAMLにして読み込む用途で利用)
pip install pyyaml

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWSCDKでVPCをデプロイする

はじめに

AWSCDKはTypeScriptやPythonといったプログラミング言語でCloudFormationのテンプレートを生成できるフレームワークです。
今回はAWSCDK(TypeScript)でVPCをデプロイする書き方を紹介していきます。

必要なパッケージ

VPC関係の定義は @aws-cdk/aws-ec2 にあります。

$ npm i -D @aws-cdk/aws-ec2

Stackファイルを書く

import cdk = require("@aws-cdk/core")
import {
  DefaultInstanceTenancy,
  RouterType,
  Vpc,
  Subnet,
  CfnInternetGateway,
  CfnVPCGatewayAttachment
} from "@aws-cdk/aws-ec2"

export class CreateVPCStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props)

    const vpc = new Vpc(this, "cdk-vpc", {
      cidr: "10.0.0.0/16",
      defaultInstanceTenancy: DefaultInstanceTenancy.DEFAULT,
      enableDnsSupport: true,
      enableDnsHostnames: true,
      subnetConfiguration: []
    })

    const pubSubnet = new Subnet(this, "PublicSubnet1c", {
      availabilityZone: "us-west-2c",
      vpcId: vpc.vpcId,
      cidrBlock: "10.0.0.0/24"
    })
    new Subnet(this, "PrivateSubnet1c", {
      availabilityZone: "us-west-2c",
      vpcId: vpc.vpcId,
      cidrBlock: "10.0.1.0/24"
    })
    new Subnet(this, "PrivateSubnet1d", {
      availabilityZone: "us-west-2c",
      vpcId: vpc.vpcId,
      cidrBlock: "10.0.2.0/24"
    })

    const internetGateway = new CfnInternetGateway(this, "InternetGateway", {})
    new CfnVPCGatewayAttachment(this, "gateway", {
      vpcId: vpc.vpcId,
      internetGatewayId: internetGateway.ref
    })

    pubSubnet.addRoute("PubSubnetRoute", {
      routerType: RouterType.GATEWAY,
      routerId: internetGateway.ref
    })
  }
}

Stackファイルの説明

VPC

各パラメータを渡してVPCを構築します。
subnetConfiguration を指定しなければいくつかSubnetが用意されます。
今回はSubnetを自分で構築したいので、 subnetConfiguration[] を指定します。

const vpc = new Vpc(this, "cdk-vpc", {
  cidr: "10.0.0.0/16",
  defaultInstanceTenancy: DefaultInstanceTenancy.DEFAULT,
  enableDnsSupport: true,
  enableDnsHostnames: true,
  subnetConfiguration: []
})

Subnets

ここではPublicのSubnetを一つとPrivateのSubnetを2つ作っています。

const pubSubnet = new Subnet(this, "PublicSubnet1c", {
  availabilityZone: "us-west-2c",
  vpcId: vpc.vpcId,
  cidrBlock: "10.0.0.0/24"
})
new Subnet(this, "PrivateSubnet1c", {
  availabilityZone: "us-west-2c",
  vpcId: vpc.vpcId,
  cidrBlock: "10.0.1.0/24"
})
new Subnet(this, "PrivateSubnet1d", {
  availabilityZone: "us-west-2c",
  vpcId: vpc.vpcId,
  cidrBlock: "10.0.2.0/24"
})

PublicのSubnetに外からアクセスするためのインターフェースを定義します。

const internetGateway = new CfnInternetGateway(this, "InternetGateway", {})
new CfnVPCGatewayAttachment(this, "gateway", {
  vpcId: vpc.vpcId,
  internetGatewayId: internetGateway.ref
})

pubSubnet.addRoute("PubSubnetRoute", {
  routerType: RouterType.GATEWAY,
  routerId: internetGateway.ref
})

さいごに

これでVPCをCDKでデプロイすることができるようになりました。
次回はこのVPCを利用してRDSのデプロイをしていこうと思います。
ではまた!!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWSの10分間チュートリアルをやってみる 2.Linux 仮想マシンの起動

こんにちは。トリドリといいます。
新卒で入社した会社でJavaを数年やった後、1年ほど前に転職してからはRailsを中心に使用してアプリケーションの開発をしているしがないエンジニアです。

今回、AWSの勉強をするために公式の10分間チュートリアルをやってみることにしたので、備忘のために記事に残していこうと思います。
AWSに関しては、1年ほど前転職活動をしていた時期にEC2とRDSを少し触っていた以外ほとんど触ったことが無い初心者です。
(ただし、このときにアカウントを作ったので、12ヶ月の無料枠は切れていました)

前回は「AWS コストの管理」を行いました。
今回は「Linux 仮想マシンの起動」をやっていきます。

Linux 仮想マシンの起動

https://aws.amazon.com/jp/getting-sdtarted/tutorials/launch-a-virtual-machine/
前述の通り、EC2は少しだけ触ったことがあります。
ただ、独学で少し触っただけでそれから1年近くほぼ触れていないため、かなり記憶の彼方へ行ってしまいました。
ということで、このチュートリアルを通して再度学びなおしていきたいと思います。

概要

EC2 = クラウド上に仮想マシンを作成したり、実行したりするためのサービス
インスタンス = AWS上の仮想マシン
という用語の説明がされています

ステップ 1:AWS にサインアップする

すでにアカウントは作成済みなので、ただログインするだけのステップです。

このチュートリアルで作成するリソースは無料利用枠の対象です

とありますが、前述の通り12ヶ月の無料枠はすでになくなっているので、有料です…

ステップ 2:Amazon EC2 インスタンスを作成する

a./b.

こちらも、AWSマネジメントコンソールからEC2ダッシュボードを開き、「Launch instance」を押すだけのステップです。
チュートリアルの画像と実際の画面はデザインが変わっています。
私の記憶にある画面はチュートリアルの方なので、1年の間にデザインがリニューアルされたのでしょう。

ステップ 3:インスタンスを設定する

a.

Amazon Machine Image (AMI)を選択します。
設定画面上にも

AMI は、インスタンスの作成に必要なソフトウェア構成 (OS、アプリケーションサーバー、アプリケーション) を含むテンプレートです。

と説明があるのは親切ですね。
無料枠の期間が過ぎたらその絞り込みも消えると思っていたので、これが残っているのも意外でした。

チュートリアルに従って 「Amazon Linux AMI」を選択します。

b.

インスタンスタイプを選択します。
インスタンスタイプは

CPU、メモリ、ストレージ、ネットワークキャパシティの組み合わせによって構成されている

ということなのでハード面ですね。
ノートPCを買う時に当てはめてみると、
AMIを決めるのはMacにするのかWindowsにするのかを決めるところで、
インスタンスタイプを決めるのは例えばMacだったらAirにするのかProにするのかを決めるところ、
みたいな感じでしょうか。

c.

ここでチュートリアルは確認と作成に進みます。
このあとのステップでサブネットを設定したりセキュリティの設定をしたりできるのは記憶にあるのですが、設定の仕方はうろ覚えなのでチュートリアルに従って私も確認と作成に進みます。
「確認と作成」はPrimaryボタンになっているので、目立つボタンをただ押すだけです。

この間のステップについては、今後の10分間チュートリアルで出てくるかは定かではないですが、出てこなければ別途勉強しようと思います。

d.

インスタンスを起動します。

起動ボタンを押すとまずはキーペアを作成するか既存のキーペアを選択するかのダイアログが表示されます。

  • キーペアにはパブリックキーというAWSに保存されるキーとプライベートキーというユーザーが保存するキーがあること
  • プライベートキーをなくしたらインスタンスにアクセスできないこと
  • 第三者がプライベートキーを入手するとその第三者がインスタンスにアクセスできてしまうので、安全な場所に保存する必要があること

がチュートリアルには記載されています。

ここではチュートリアルに従ってキーペアを作成し、.sshディレクトリに保存します。

キーペアの設定後に「インスタンスの起動」ボタンを押すとインスタンスが起動します。

e.

ボタンを押してインスタンス一覧に戻るだけのステップです。

f.

インスタンスの一覧です。
作成したインスタンスは、「インスタンスの状態」がrunnningになれば起動完了です。
画面の上部には一覧が、下部には選択したインスタンスの詳細が表示されています。

チュートリアルに従って、下部の「説明」タブからIPv4パブリックIPをコピーしておきます。

ステップ 4:インスタンスに接続する

作成したインスタンスにSSHを利用して接続します。
とりあえず、SSH = ネットワークで接続された機器同士で安全に通信するための手段ということがわかっていれば先に進めそうです。

[SSHについて詳しそうな記事]
https://qiita.com/Pana/items/70cc74cd20c8b5b4fe15
https://qiita.com/angel_p_57/items/2e3f3f8661de32a0d432
https://qiita.com/angel_p_57/items/c441aa469107136aacc5
https://qiita.com/tag1216/items/5d06bad7468f731f590e

私の環境がMacなので、Macの手順で進めます。

a.

SSHで接続できるかを確認するため、ターミナルで下記コマンドを実行します。

$ ssh

下記のように出てくれば、SSHクライアント(SSHで接続する方のプログラム)が存在するので、OK

usage: ssh

b.

chmodコマンドを使用して、プライベートキーのファイルに対し、所有者だけがアクセスできるように権限の設定をします。
コマンドは下記の通り。

chmod 400 { プライベートキーのパス }

c.

インスタンスに接続します。
コマンドのフォーマットは次の通りです。

ssh -i { プライベートキーのフルパス } { ユーザー名 }@{ インスタンスのIPアドレス }

Amazon Linux AMIのインスタンスではユーザー名はec2-userなので、

ssh -i { プライベートキーのフルパス } ec2-user@{ インスタンスのIPアドレス }

となります。

初めての接続時は、

The authenticity of host 'IPアドレス (IPアドレス)' can't be established.
Are you sure you want to continue connecting (yes/no)? 

と聞かれるので、yesと打ち込みます。

d.

       __|  __|_  )
       _|  (     /   Amazon Linux AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-ami/2018.03-release-notes/

のようにウェルカム画面が表示されれば接続成功です。

今回の詰まったところ
最初に接続しようとすると何度やってもタイムアウトしてしまいました。
ググって見つけた下記記事を参考にVPCの確認をした結果、時期の通りルートテーブルが正しく設定されていませんでした。
https://qiita.com/yuta-ushijima/items/0c8fbcf315a850c636f4
1年前の自分が触って放置した結果…
とりあえず記事の通りに設定して接続できましたが、VPCとかサブネットとかも記憶の彼方へ行ってしまったので、また勉強し直したいと思います。

ステップ 5:インスタンスを削除する

a.

EC2コンソールのインスタンスで画面上部のアクションから「terminate」を選択します。
日本語にしているとstopが停止、terminateが終了でぱっと見分かりづらいので英語のほうが良いと思います。

b.

ダイアログで「Yes, Terminate」を押すと、インスタンスが削除されます。
チュートリアルでa.の「terminate」は英語なのに、ここの「Yes, Terminate」は日本語なのが気になる…

まとめ

難しいことは考えず、とりあえず起動して接続して削除するだけのチュートリアルでした。
1年前に少し触っていた分、このチュートリアル範囲以上のことはほとんど忘れてしまっていることにも気がつけたので良かったのですが、初めてでこれをやるのはそれ以外の範囲が広がっていることを認識するのが難しそうに思ってしまいました。
本当に10分間でさわりだけ体験できる感じなので、ちゃんと勉強するには本などで体系立ててやる必要がありそうですが、とりあえず「ウェブサイトとウェブアプリ」のチュートリアルについてはこのまま進めて行こうと思っています。

次回は、「WordPress ウェブサイトの起動」に進む予定です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

なぜ僕たちはサーバレスでJavaを諦めTypescriptを採用したか

この記事は個人ブログのうち技術に関する箇所のみを抜粋した転載です。
なぜ僕たちはサーバレスでJavaを諦めTypescriptを採用したか -Junks, GC cannot sweep-

またブログには書いたのですが、諸事情により先に英語版が存在します。
こちらも書いたのは僕なので、剽窃などではないことはご了承ください。

[元記事]: Why we replaced Java with Typescript for Serverless in dev.to

はじめに

サーバレス(serverless)は昨今もっとも注目を集める設計手法の一つで、おそらく多くの開発者が大なり小なり自分のプロダクトに応用し始めているのではないでしょうか?

僕自身、完全にサーバレスに魅せられてしまい、昔ながらの自分でサーバやミドルウェアを管理しながら運用するみたいな世界には戻れる気がしません。

そもそもスケーリングや分散可能性をきちんと考えて設計されたアプリケーションであれば、旧来のサーバーアプリケーションの機能から受けられる恩恵も比較的少なくなりますし、サーバレスに切り替えるデメリットはそこまでありません。
最近は設計に関して相談された時は、必ずサーバレスの話題を出してみることにしています。

さて、サーバレスは既存の開発手法とは大きく異なるため、今持っている知識を刷新し、既存の手法や技術スタックを見直しながら開発していく必要があります。
見直しというからには、開発基盤として何の言語を使うかも、当然ながら見直さなくてはいけない技術スタックの対象になります。

タイトルにある通り、最終的に僕たちはTypescriptを採用し、およそ一年半開発・メンテナンスを行ってきました。
そして一年半経った今、あくまで個人的な感想ではありますが、Typescriptは僕たちが期待した以上に成果を出してくれました。

そこでこの記事では、以前使用していた言語にどんな問題があったのか、そしてなぜTypescriptに切り替えたことでどんな恩恵があったのかをこの記事では解説していきたいと思います。

なぜJavaを諦めなくてはならなかったのか

さて、なぜTypescriptを採用したかについて話す前に、まずなぜ以前使用していた非常に強力な言語であるJavaを諦めなくてはいけなかったかについてお話ししたいと思います。


NOTE
先に述べておきますが、僕は結構なJava好きです。なんなら初めて触った言語もJavaでした。
JVMに関してもそれなりに勉強して、その神がかったランタイムの仕組みにかなり感銘を受けています。(てか多分作ったやつは神)
なので、どこかの大学生のようにJavaがクソだとかレガシーだとか使い物にならんとか、この記事でそういうことを言うつもりは一切ありません。
また、そういったコメントもあまり嬉しくないです。あくまでサーバレスという仕組みにJavaがあまり合わなかっただけなので。
その点だけはご了承いただければ幸いです。


さて、本題に戻りましょう。

僕たちのサービスでは、サーバサイドはサービス設立当時から基本的にJavaだけで書かれていました。
当然ながらすでにJavaには多くの利点があり、特に

  • プラットフォームフリー
  • よくできたJITコンパイル
  • やばいGC
  • よく構成された文法
  • 静的型付け
  • 関数型サポート(最近は特に)
  • 多様なライブラリ
  • 信頼できるコミュニティ(Oracleではなく、開発者の方)

などなど挙げればきりがありません。

しかし、AWS Lambda上でコードを試していて気づいたのですが、Javaはあまりサーバレスに向かないことがわかりました。

理由としては以下のことが挙げられます。

  • JVMの起動オーバーヘッドが大きい
  • Springフレームワークを使用してるとさらにエグくなる
  • 最終的なパッケージアーカイブがでかすぎる(でかいのは100MB以上)
  • 関数が増えてくるとWebフレームワークなしでリクエストを捌くのがきつくなる
  • コンテナは30分程度しか走らないため、G1GCやJITなどのJavaの利点が生かせない
  • Lambdaは基本的にEC2上に建てられたAmazon Linuxのコンテナで動くため、プラットフォームフリーは関係ない。 (欠点ではないけど)

上述の点は全てなかなかに厄介ですが、今回は特に厄介だった問題についてもう少し書いてみたいと思います。

Cold Startがまじで厄介

一番厄介だったのは、圧倒的にCold Startのオーバーヘッドです。おそらく多くの開発者の方々もこいつに悩まされているのではないかと思います。。。

僕たちはコンピューティング基盤としてAWS Lambdaを使っていたのですが、AWS Lambdaはユーザからのリクエストが来るたびに新しいコンテナを立ち上げます。

一度立ち上がってしまえば、しばらくは同じコンテナインスタンスを再利用してくれるのですが、初回起動時にはJavaのランタイムに加え、フレームワークで利用されるDIコンテナやWebコンテナなども全て初期化する必要があります。

さらに言えば、一つのコンテナで処理できるのはあくまで一つのリクエストのみで、複数のリクエストを処理することはできません。(内部で数百のリクエストスレッドをプーリングしてたとしても同じです。)

つまりどういうことかというと、もし複数のユーザがリクエストを同時に送ってきた場合、Lambdaは起動中のコンテナの他に、別のコンテナを起動しなくてはいけなくなるということです。
通常、僕たちはどの時間に具体的に何軒のリクエストが同時に来るかを事前に予測することはできません。
つまり、何らかの仕組みを作ったとしても、事前に全てのLambdaをhot standbyさせることはできないのです。

これは必然的にユーザに数秒から10秒以上の待機時間を強制し、ユーザビリティを著しく下げることにつながります。

こんな感じでCold Startがえげつない事を理解した僕らは、これまでの数年かけて書かれた技術スタックを捨てて、他の言語を選択することを決めました。

なぜTypescriptを選んだのか

めちゃくちゃ恥ずかしい話なのですが、正直Lambdaでサポートされている全ての言語をきちんと精査・比較して決めたわけではないのです。
ただ、正直な話、状況的にTypescript以外の選択肢はなかったのです。

まず第一に、動的型付け言語は外しました。長期に渡ってスキルのバラバラな開発者によって保守・メンテ。拡張されるコードなので、動的型付けはあまり使いたくありません。

したがって、PythonRubyに関してはかなり序盤で選択肢から外れました。

C#Goに関しても、現在ほとんどのチームがJavaをメインに開発しているサービスなので、既存言語とあまりかけ離れた言語を使うと新規開発者のジョインが難しくなると判断し、今回は見送られました。

もちろん、昨今この二大言語は非常に注目度が高く、特にGolangに関しては徐々にシェアを伸ばしつつあるのは知っています。
しかし、急いでサーバレスに開発を移す必要があったため、僕たち自身のキャッチアップの時間も考慮し、見送らざるを得なかった感じでした。

Typescriptの利点

という事で、僕たちはTypescriptを使い始めたわけです。
Typescriptの利点を挙げるとしたらこんな感じでしょうか?

  • 静的型付け
  • 小さいパッケージアーカイブ
  • ほぼ0秒の起動オーバーヘッド
  • Javaとjavascriptの知識が再利用できる
  • NodeJSのライブラリやコミュニティが使える
  • javascriptと比べても関数型プログラミングがしやすい
  • ClassとInterfaceにより構造化されたコードが描きやすい

長期に渡って運用・開発が行われるプロジェクトにおいて静的型付け言語がどれだけ大きな恩恵を与えるかは今更語るまでもありませんので、ここには書きません。
ここでは主に、Typescriptのどういった点がサーバレス開発によく馴染んだかについて書いていきたいと思います。
静的型付け以外にもTypescriptを使う利点は非常に大きく、

小さいパッケージと小さい起動オーバーヘッド

おそらくサーバレスでTypescriptを使う利点という観点からいうとこれが一番大事だった気がします。(なにせ他のメリットはほぼTypescript自体のメリットなので・・・)

先ほど触れた通り、JavaはJVM本体やフレームワークが利用するDI/Webコンテナなどの起動にかかるオーバヘッドが非常に大きいです。
加えて、Javaの性質上、AWS Lambdaで流すには以下の
Additionally, as the nature of Java, it has the following weak point to be used in the AWS Lambda.

マルチスレッドとそれを取り巻くエコシステム

マルチスレッドは非常に強力な機能であり、事実として僕たちはこの機能のおかげで多くのパフォーマンス問題を解決してきました。
JVM自体もガーベージコレクションやJITコンパイルにおいて、デフォルトでにマルチスレッドを活用してあの素晴らしいランタイムを実現してます。
(詳しくはG1GCJIT Compileを参照)

しかし、起動時間単体で見ると、アプリケーションに使用する全てのスレッドを立て終わるまでに、100ミリ秒から数秒かかっていることがわかります。
この機能自体は旧来のいわゆるクラサバモデルでEC2上で動くアプリケーションならほぼ無視できるオーバーヘッドですが、LambdaなどのFaaS上で動くサーバレスアプリケーションでは決して無視できません。

Typescriptはnodejsベースであり、基本的にシングルスレッドです。非同期は別スレッドや別プロセスではなくあくまでジョブキュー、イベントループなどで管理されます。

したがって、ほとんどのライブラリやフレームワークは起動時にスレッド展開をする必要はありませんし、ランタイムを起動するためのオーバーヘッドもほとんどかかりません。

巨大なパッケージアーカイブ

サーバレスにおいてソースコードのパッケージアーカイブは、基本的に小さいに越したことはありません

Lambdaのコンテナは起動時、AWSにより管理されたソースコード用のS3バケットからソースをダウンロードし、コンテナに展開します。

S3からのダウンロード時間は通常非常に短時間ですが、100MBや200MBとなると無視はできません。

NodeJsのパッケージは基本的にJavaに比べて小さくなります。

正直なんでそうなるかに関しては不勉強でわかっていないのですが、以下の理由が関係してるんじゃないかなと思ったりします。(もしこれやでっていうのをご存知の方はコメントで教えてください)

  • Javaのフレームワークやライブラリは包括的なものも多く、本来使いたい機能に必要ない依存性を引き込んで来るが、javascriptは目的特化のライブラリが多く、必要最低限に依存を抑えられることが多い。
  • Javascript(nodejs)は1ファイルに複数のmoduleを書くことができ、それでいてメンテもしやすいが、Javaにおけるメンテナンス性の重要なポイントはファイル分割とパッケージ管理のためソースが肥大化しやすい。

実際Javaで書いていた時は最大で200MB以上のパッケージができることもあったのですが、nodejsに変えてからは35MB程度で済んでいます。

この巨大なパッケージアーカイブは、僕たちがSpringで書かれた旧来のコードを再利用しようとしたのが大きな原因なのですが、実際これらのいらないフレームワークを除いて最適化したコードでも、どうしても50MBは必要になってしまいました。

Javascriptの知識やエコシステムを利用できる

僕たちもWeb開発者のため、基本的にフロントエンドも書きます。したがって、ある程度のjavascriptやnodejsに関する知識は蓄えていました。

Jquery全盛時代からReact/Vueのようなモダンフレームワークでの開発までを通じて、言語的な特徴はある程度抑えていましたし、どうやって書けばいいコードになるかもある程度理解してるつもりです。

Typescriptはjavascriptの拡張言語であり、最終的にはjavascriptにトランスパイルされます。

多くの文法やイディオムはjavascriptから受け継がれているので、実際それほど準備期間を要さずにサービス開発を始められました。

加えて、ほとんどのメジャなNodeJSのライブラリはTypescriptに必要な型定義を提供しているので、NodeJSのエコシステムのメリットをそのまま享受できたのも非常に嬉しいポイントでした。

関数型の実装が非常にしやすい

昨今の技術トレンドを語る上で、関数型の台頭はなくして語ることはできません。
関数型の実装はその性質上、シンプルでテスト可能で危険性の低い安定したコードを書くのに大きく寄与します。

特にAWS Lambdaの場合、常に状態を外部化するコードが求められるため、状態や副作用を隔離する関数型の実装は非常に相性が良く、メンテもしやすくなります。

そもそも、jqueryの生みの親であるJohn ResigがJavaScriptニンジャの極意で語ったように、javascriptはそもそも関数型のプログラミングをある程度サポートしています。
javascriptにおいて関数は関数は第1級オブジェクトであり、jqueryも実は関数型で書かれることを期待して作られています。

しかし一方で、動的型付け言語で関数型のコードを書こうとすると、時折非常にめんどくさい事になることがあります。
例えば、プリミティブ型だけで表現できる関数は非常に限られますし、返り値や引数にObjectを取るのは普通に結構危険です。

しかしtypescriptでは引数や返り値に型を指定することができます。

加えて、以下のTypescriptの機能は、僕たちの達の書く関数の表現の幅を広げ、より安全でシンプルなコードを書くのに寄与してくれます。

  • Type: 共通に使用される型をコンテクストに合わせて型付けできる。(stringUserIdPromiseResponseなど)
  • Interface/Class: Objectで表現されるの引数や返り値をコンテクストにあった型で表現できる。
  • Enum: よもや語る必要もあるまい
  • Readonly: 自分で作成した型をImmutableに出来る
  • Generics: 関数のインターフェイスの表現の幅が広がる

Typescriptは他にも関数型で書こうとした時に非常に便利な機能をいろいろ備えていますが、全てをここであげることはしません。(っていうか、結構javascript由来のものが多い)

関数型とTypescriptに関する記事は今後どこかで書いていきたいなと思っています。

Javaで学んだBest Practiceを再利用できる

typescriptの文法を学ぶと、かなりJavaやScalaに似通った記述ができることに驚きます。

僕たちはそもそも、それなりの期間をJavaで開発してくる中で、Javaにおけるいいコードのお作法をある程度蓄積してきました。
ClassやInterfaceをどう設計すべきか、enumはどう使うと効率的か、Stream APIはどう書くと保守性が上がるかなど、蓄積してきたノウハウはそれなりに捨てがたいものがありました。

Typescriptはインターフェイスやクラスに加えて、アクセスモディファイアやreadonly(Javaでいうfinalのプロパティ)をサポートしており、僕たちは割とさらっとJavaで育んだノウハウをそのまま導入することができました。

これにはオブジェクト指向的なベストプラクティスやデザインパターンなども含まれます。
(関数指向とオブジェクト指向は二律背反ではないので、プロジェクト内で同時に使用されても問題ないと考えています。個人的には。)

もし僕たちがやや文法が独特なPythonやRubyを採用していたとしたら、より品質の高いコードを書くためのプラクティスをどうこの言語に応用すべきかに多くの時間を費やすこになったことかと思います。(それも楽しいんですよ、知ってます、ただ時間がね。。。。)

当然ながら全ての設計やロジックをコピペしたわけではないですし、むしろ大半のコードを書き直ししました。
ただ、おおよその部分をスクラッチで書き直した割に、それなりの品質でそれなりの短期間で書き直しが終わったんだよということは特筆しておくべきかと思います。

結論

僕たちもまだまだTypescriptに関しては初心者といっていいレベルでまだまだ勉強が必要ですが、すでにそのメリットは全力で享受しておりいます。

今聞かれれば、Golangもいいなあとか、MicronautとGraalVMとかも面白そうだなあとか、もっと他の選択肢もあったかもなあとか考えたりもするのですが、現状Typescriptには非常に満足しており、最善の選択肢の一つではないかと信じています。

もちろん、処理遅いけどバッチ処理どうすんねんとか、並行処理とか分散処理同すんねんとか、、ワークフロウどう設計すんねんとか、API Gatewayのタイムアウトどうハンドルするねんとか、データの一貫性どう担保すんねんとか、サーバレスやTypescriptに起因する問題にはたくさんぶち当たりました。

ただ、それはそれでギークとして非常に楽しく取り組んできて、すでにいくつかのこれが今の所best practiceじゃね?っていう方法もいくつか見つけました。(これはのちのち記事にしていきたい。)

もし今Javaでサーバレスに取り組んでいて、サーバレスくそやん、きついやん、やっぱ普通にサーバ欲しいわってなっている方がいたら、ぜひTypescriptも試してみてください。想像する以上に生産性出るんじゃないかなぁって期待してます。

長文おつきあいいただきありがとうございました。何かコメントや訂正があればぜひお願いします。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS EKSを使用したKubernetes自動構築ツール作った

Kubernetes構築構築ツール作りました!

やっと出来ましたk8s構築ツール(仮)です!長かった。。Ansibleとシェルスクリプト触ったの久々だしTerraformは初めて触るしで時間にして丸々4日くらいかかりました。。

k8s環境構築ツールを作ろうとした理由

  • 半年くらい前に少しだけ触ってたが本格的にk8sの知識が必要になりく勉強する必要が出てきたため。
  • 一々手動で環境構築するのが手間だと感じたため

AWS EKSを使用した理由

k8s勉強用のVM作るのは難易度が高そう

VMを使用してk8s環境作ろうとするとMaster Node用とWorker Node用にVMを複数作成してそれぞれネットワーク接続できるようにして更にもっと作業して。。と今の自分の保有スキルに対して作業難易度が高めかなぁと思ったからです。(ただ今はネスぺの勉強もしてるので近いうちに挑戦してみたいです!)

AWS触ってみたいから

昨今AWSを使用してるPJが周囲でも増えてきたため、仮に別PJに移ったとして「AWSって聞いたことあるけど分からないマン」となるのは避けなければと思ったからです。

EKSが半額

正直このニュース見て「やらねば(使命感)」となったことが一番の理由かもです 笑

ツールの使い方

前提条件

  • AWSアカウントを作成してIAMユーザが作成してあること
  • ローカルマシンにvagrantがインストールされてること

k8s環境作成

  1. mkdir <作業用ディレクトリ> && cd <作業用ディレクトリ>
  2. vagrant init ubuntu/xenial64
  3. vagrant up
  4. vagrant ssh
  5. ログイン後以下コマンドを実行
    git clone https://github.com/arthur-maki/eks_env_tool.git -b feature-add_iam_and_vpc
  6. cd eks_env_tool
  7. source k8s_env.sh
  8. createを入力した後以下を入力
    • aws_secret_access_key
    • aws_access_key_id
    • env_name
    • VpcBlock(VPC)のCIDER
    • PublicSubnet01BlockPublicSubnet02BlockのCIDER
    • PrivateSubnet01BlockPrivateSubnet02BlockのCIDER
  9. うまくいけば構築完了

k8s環境削除

  1. cd eks_env_tool
  2. source k8s_env.sh(※)
    ※ virtualenvが起動してる時((eks_env) ユーザ@ホスト)は必ずdeactivateコマンドを実行してユーザ@ホストになることを確認してから実行
  3. destroyを入力した後環境構築した時と同じパラメータを入力
  4. うまくいけば削除成功。念のため、各AWSサービスを見て削除されてるか確認

[捕捉]

構築した時とは異なるvagrant環境でも削除できるように作ってあるため、vagrant init ubuntu/xenial64で別環境を作ってからk8s環境を削除しても良い

確認した動作環境

  • vagrantのubuntu/xenial64(latestバージョン)
  • vagrantユーザ
    本来であれば一般ユーザでも確認するべきだが多分大丈夫だと思う。。

今後やりたいこと

ソースコードの見直し

シェルスクリプトとAnsibleコードがありえないくらい汚い。というよりファイル名も適切じゃないかもだし変数名もひどい。全体的に見直す必要あり。というか心が仏の優しいどなたかレビューしてください。。

他ubuntuバージョンだったり他OSに対応

需要があれば対応していきたい

アドバイスやその他意見があればドシドシください!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む