20210228のAWSに関する記事は30件です。

AWS日記24 (Cognito - OIDC)

はじめに

今回は AWS Cognito の OpenID Connect (OIDC) を試します。
OIDCを利用しGitHubとアカウント連携します。

準備

AWS SAM の準備をします

[AWS Cognitoの資料]
AWS Cognito
ユーザープールへの OIDC ID プロバイダーの追加

AWS SAM テンプレート作成

AWS SAM テンプレートで API-Gateway , Lambda, AWS Cognito の設定をします。

[参考資料]
AWS SAM テンプレートを作成する

AWS Cognito設定用のSAMテンプレート

AWS Cognito の設定は以下の部分

AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: Serverless Account Page

Parameters:
  ApplicationName:
    Type: String
    Default: 'ServerlessAccountPage'
  UserPoolClientName:
    Type: String
    Default: 'GitHubOpenIdAuthClient'
  UserPoolDefaultDomainPrefixName:
    Type: String
    Default: 'test-user-pool-domain2021'
  UserPoolClientCallbackURL:
    Type: String
    Default: ''

Resources:
  UserPool:
    Type: AWS::Cognito::UserPool
    Properties:
      AdminCreateUserConfig:
        AllowAdminCreateUserOnly: false
        UnusedAccountValidityDays: 7
      AutoVerifiedAttributes:
        - 'email'
      MfaConfiguration: 'OFF'
  UserPoolClient:
    Type: AWS::Cognito::UserPoolClient
    Properties:
      UserPoolId: !Ref UserPool
      ClientName: !Ref UserPoolClientName
      AllowedOAuthFlows:
        - 'implicit'
      AllowedOAuthFlowsUserPoolClient: true
      AllowedOAuthScopes:
        - 'openid'
      ExplicitAuthFlows:
        - 'ALLOW_REFRESH_TOKEN_AUTH'
      GenerateSecret: false
      CallbackURLs:
        - !Ref UserPoolClientCallbackURL
  UserPoolDomain:
    Type: AWS::Cognito::UserPoolDomain
    Properties:
      UserPoolId: !Ref UserPool
      Domain: !Ref UserPoolDefaultDomainPrefixName

Outputs:
  APIURI:
    Description: "Amazon Cognito Domain"
    Value: !Join [ '', [ 'https://', !Ref UserPoolDomain, '.auth.', !Ref 'AWS::Region', '.amazoncognito.com'] ]

GitHub OAuth App ラッパー用のSAMテンプレート

AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: Serverless Cognito GitHub OpenId Page

Globals:
  Function:
    Runtime: go1.x
    Timeout: 15
    Environment:
      Variables:
        REGION: !Ref 'AWS::Region'
        ALGORITHM: !Ref Algorithm
        KEY_ID: !Ref KeyId
        CERT_PATH: !Ref CertPath
        PUB_KEY_PATH: !Ref PubKeyPath
        GITHUB_CLIENT_ID: !Ref GitHubClientId
        GITHUB_CLIENT_SECRET: !Ref GitHubClientSecret
        COGNITO_REDIRECT_URI: !Ref CognitoRedirectUri
        GITHUB_API_URL: !Ref GitHubUrl
        GITHUB_LOGIN_URL: !Ref GitHubLoginUrl

Parameters:
  GitHubClientId:
    Type: String
  GitHubClientSecret:
    Type: String
  CognitoRedirectUri:
    Type: String
  GitHubUrl:
    Type: String
    Default: "https://api.github.com"
    MinLength: 1
  GitHubLoginUrl:
    Type: String
    Default: "https://github.com/login"
    MinLength: 1
  StageName:
    Type: String
    Default: "CognitoGitHubApiStage"
  StageName:
    Type: String
    Default: "CognitoGitHubApiStage"
  CertPath:
    Type: String
    Default: "jwtRS256.key"
  PubKeyPath:
    Type: String
    Default: "jwtRS256.key.pub"
  Algorithm:
    Type: String
    Default: "RS256"
  KeyId:
    Type: String
    Default: "jwtRS256"

Resources:
  GithubOAuthApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: !Ref StageName
      OpenApiVersion: "2.0"
  OpenIdDiscovery:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: api/discovery/bin/
      Handler: main
      MemorySize: 256
      Events:
        GetResource:
          Type: Api
          Properties:
            Path: /.well-known/openid-configuration
            Method: get
            RestApiId: !Ref GithubOAuthApi
  Authorize:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: api/authorize/bin/
      Handler: main
      MemorySize: 256
      Events:
        GetResource:
          Type: Api
          Properties:
            Path: /authorize
            Method: get
            RestApiId: !Ref GithubOAuthApi
  Token:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: api/token/bin/
      Handler: main
      MemorySize: 256
      Events:
        GetResource:
          Type: Api
          Properties:
            Path: /token
            Method: get
            RestApiId: !Ref GithubOAuthApi
        PostResource:
          Type: Api
          Properties:
            Path: /token
            Method: post
            RestApiId: !Ref GithubOAuthApi
  UserInfo:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: api/userinfo/bin/
      Handler: main
      MemorySize: 256
      Events:
        GetResource:
          Type: Api
          Properties:
            Path: /userinfo
            Method: get
            RestApiId: !Ref GithubOAuthApi
        PostResource:
          Type: Api
          Properties:
            Path: /userinfo
            Method: post
            RestApiId: !Ref GithubOAuthApi
  Jwks:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: api/jwks/bin/
      Handler: main
      MemorySize: 256
      Events:
        GetResource:
          Type: Api
          Properties:
            Path: /.well-known/jwks.json
            Method: get
            RestApiId: !Ref GithubOAuthApi

Outputs:
    CognitoGitHub:
      Description: "Cognito GitHub Api URL"
      Value: !Sub "https://${GithubOAuthApi}.execute-api.${AWS::Region}.amazonaws.com/${StageName}"

フロントエンド用のSAMテンプレート

AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: Serverless Cognito OpenId FrontPage

Parameters:
  ApplicationName:
    Type: String
    Default: 'ServerlessCognitoOpenIdFrontPage'
  CognitoUrl:
    Type: String
    Default: 'https://your-domain-prefix.auth.your-region.amazoncognito.com'
  CognitoClientId:
    Type: String
    Default: 'abc012'

Resources:
  FrontPageApi:
    Type: AWS::Serverless::HttpApi
  FrontPageFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: ServerlessCognitoOpenIdFrontFunction
      CodeUri: bin/
      Handler: main
      MemorySize: 256
      Runtime: go1.x
      Description: 'Cognito OpenId Front Function'
      Environment:
        Variables:
          REGION: !Ref 'AWS::Region'
          COGNITO_URL: !Ref CognitoUrl
          COGNITO_CLIENT_ID: !Ref CognitoClientId
      Events:
        FrontPageApi:
          Type: HttpApi
          Properties:
            Path: '/'
            Method: get
            ApiId: !Ref FrontPageApi
        FrontProxyApi:
          Type: HttpApi
          Properties:
            Path: '/{proxy+}'
            Method: get
            ApiId: !Ref FrontPageApi

Outputs:
  APIURI:
    Description: "URI"
    Value: !Join [ '', [ 'https://', !Ref FrontPageApi, '.execute-api.',!Ref 'AWS::Region','.amazonaws.com/'] ]

Lambda関数作成

※ JWT周りの処理は github.com/dgrijalva/jwt-go を利用しました。

    res.Algorithm = os.Getenv("ALGORITHM")
    res.KeyId = os.Getenv("KEY_ID")
    keyData, err := os.ReadFile(os.Getenv("PUB_KEY_PATH"))
    if err != nil {
        return res, err
    }
    key, err := jwt.ParseRSAPublicKeyFromPEM(keyData)
    if err != nil {
        return res, err
    }
    nBytes := (*(*key).N).Bytes()
    eBytes := []byte(strconv.Itoa((*key).E))

終わりに

外部サービス(GitHub)の設定を行う必要があるため、SAMテンプレートを複数に分ける必要がありました。
GitHubだけでなく他の外部サービスにも応用ができるため、今後試そうと思います。

参考資料

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

EC2のインスタンスタイプの選び方

はじめに

初めての投稿です。
EC2インスタンスを選択する際、様々なタイプがあって迷うと思います。
その際に参考になればという思いで記事を書きました。

インスタンス起動しても直ぐに落ちていた

ただシステムにアクセスするだけで1日に何度もシステムダウンしていた。

原因

例えば簡単なページを表示する分には、無料枠の t2.micro で問題ない。
データベース等重いシステムを動かず場合は、これでは動かない。
CPUが直ぐにいっぱいになってシステムダウンしてしまう・・・
よく確認すると t2.micro = 1Gib って書いてある・・・これじゃ動かないよね。

対策

なので、t2.micro => t3.medium に変更しました。
これでメモリは1Gib => 4Gib 単純に4倍!
料金は0.0152USD/時間 => 0.0544USD/時間

最初は料金も安い事から t3a.medium を選択しましたが、今回のインスタンスタイプは1cだったので、このAZでは未対応だった為に選択できず t3.mediumを選択しました。

結果

これで無事にシステムダウンすることがなくなりました!

参考にさせて頂いた記事

https://pages.awscloud.com/rs/112-TZM-766/images/C2-07.pdf​​

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

一問一答で学ぶAWS責任共有モデル

AWSクラウドプラクティショナーの資格試験の学習を兼ね、
一問一答形式でAWS責任共有モデルについて記載しています。

今後、追記予定です。
(内容が薄い&インフラ初心者ではあります。ご容赦の上、ご覧ください。)

概要

Q.責任共有モデルって何?

A.AWS側の責任、ユーザ側の責任を定め、全体のセキュリティを担保しようという考え方

Q.元ネタは?

A.英語で、shared responsibility modelの日本語訳。
ただし、ここでのsharedは「共有」という意味合いより「分担」という意味合いの方が正確

Q.何故こういった考え方が存在する?

A.責任の所在を明確するため

Q.責任の分界点を一言で言うと?

A.クラウド自体(AWS)か、クラウド内(ユーザー)か

Q.もう少し詳しく

A.AWS側は物理的なインフラや、仮想環境を動かすハイパーバイザーに対し、そしてユーザー側はOSより上のレイヤーに対し責任を負う

Q.ちなみに、ここでいうクラウドって何よ?

A.インターネット経由でさまざまなITリソースをオンデマンドで利用することが出来るサービスの総称

Q.オンデマンドって何?

A.ユーザの要求に応じて、サービスを提供すること

Q.他のクラウド事業者も同じような責任の分担?

A.サービスモデル(IaaS,PaaS,SaaS)によって異なるから、同じとは言い切れない
(国際的なクラウド事業者は、各々責任共有モデルに対応する責任分担について表明している)

問題

Q.利用者がインストールしたOSやソフトウェアのトラブルの責任はどちらにある?

A.利用者側

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

[Ansible]EC2のメタデータを変数に格納する

pre_tasksでec2のメタデータを収集する。

pre_tasks:
    - name: gather ec2_metadata
      amazon.aws.ec2_metadata_facts:

収集できるメタデータ(公式サイト)

変数に格納

例) "public_ip"にEC2のパブリックIPを格納したい場合、下記のように変数を設定しておく

public_ip: "{{ ansible_ec2_public_ipv4 }}"

使い所

AWXのワークフロー使用時、CloudFormationで新規構築したEC2のIPを後続タスクで使用する変数に格納したい時などに使えます。

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

AWS Amplifyの認証画面を多言語対応してみた

はじめに

Ionic(Angular)環境でAWS Amplifyの認証画面を多言語対応してみました。
Angular環境の場合は、ionタグの部分を無視して読んで下さい。
AWS Amplifyの環境構築は、こちら
=>[Ionic6 + Angular11]AWS AmplifyとIonicでさくっと認証画面を作成する

2021年2月時点

思い

  1. できるだけ、コストをかけずに認証画面を作成したい。
  • 認証画面を自作するコストをかけたくないが、AWS Amplifyの認証画面がカスタマイズできず結局自作というケースも多いのでは?

完成画面

selectLanguage.gif

ライブラリのバージョン

  • aws-amplify": 3.3.20
  • @aws-amplify/ui-angular: 1.0.2

対応の流れ

  1. aws-amplifyにある I18nモジュールを読み込む。
  2. 辞書ファイルを用意し、I18nに渡す。
  3. 表示言語を設定する。

拡張

  1. 画面上から言語を切り替えられるようにした。

対応(変更点)

home.page.ts
- import { Component, ChangeDetectorRef } from '@angular/core';
+ import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import {
  onAuthUIStateChange,
  CognitoUserInterface,
  AuthState,
  FormFieldTypes,
} from '@aws-amplify/ui-components';
import { I18n } from 'aws-amplify';
import { amplifyVocabularies } from '../shared/const/amplify.vocabularies.const';

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})
- export class HomePage {
+ export class HomePage implements OnInit, OnDestroy {
  title = 'amplify-angular-auth';
  user: CognitoUserInterface | undefined;
  authState: AuthState;

  /** サインアップフィールド定義 */
  signUpformFields: FormFieldTypes = [
    { type: 'username' },
    { type: 'password' },
    { type: 'email' },
  ];

+   languageOptions = [
+     {
+       label: '日本語',
+       value: 'ja',
+     },
+     {
+       label: 'English',
+       value: 'en',
+     },
+   ];

+   labelSelectLanguage = 'Select Language';
+   selectedLang = 'en';

-  constructor(private ref: ChangeDetectorRef) {}
+   constructor(private ref: ChangeDetectorRef) {
+     const lang = sessionStorage.getItem('lang');
+     if (lang === null) {
+       return;
+     }
+     I18n.putVocabularies(amplifyVocabularies);
+     I18n.setLanguage(lang);
+     this.selectedLang = lang;
+     this.labelSelectLanguage = I18n.get('Select Language');
+   }

  ngOnInit() {
    onAuthUIStateChange((authState, authData) => {
      this.authState = authState;
      this.user = authData as CognitoUserInterface;
      this.ref.detectChanges();
    });
  }

  ngOnDestroy() {
    return onAuthUIStateChange;
  }

+   onChange(): void {
+     sessionStorage.setItem('lang', this.selectedLang);
+     location.reload();
+   }
+ }

home.page.html
<ion-content [fullscreen]="true">
  <div id="container">
    <amplify-authenticator *ngIf="authState !== 'signedin'">
      <amplify-sign-in slot="sign-in">
+        <div slot="federated-buttons">
+          <div class="select-language">
+            {{labelSelectLanguage}}
+            <select [(ngModel)]="selectedLang" (change)="onChange()">
+              <option *ngFor="let languageOption of languageOptions" [ngValue]="languageOption.value">
+                {{languageOption.label}}
+              </option>
+            </select>
+          </div>
+        </div>
      </amplify-sign-in>
      <amplify-sign-up slot="sign-up" [formFields]="signUpformFields">
      </amplify-sign-up>
    </amplify-authenticator>

    <div *ngIf="authState === 'signedin' && user" class="App">
      <amplify-sign-out></amplify-sign-out>
      <div>Hello, {{user.username}}</div>
    </div>
  </div>
</ion-content>

言語用constと型定義


かなり長いので折りたたみ
amplify.vocabularies.const.ts
// Ref https://github.com/aws-amplify/amplify-js/blob/main/packages/amplify-ui-components/src/common/Translations.ts
/* eslint-disable @typescript-eslint/naming-convention */
const AuthStrings = {
  BACK_TO_SIGN_IN: 'Back to Sign In',
  CHANGE_PASSWORD_ACTION: 'Change',
  CHANGE_PASSWORD: 'Change Password',
  CODE_LABEL: 'Verification code',
  CODE_PLACEHOLDER: 'Enter code',
  CONFIRM_SIGN_UP_CODE_LABEL: 'Confirmation Code',
  CONFIRM_SIGN_UP_CODE_PLACEHOLDER: 'Enter your code',
  CONFIRM_SIGN_UP_HEADER_TEXT: 'Confirm Sign up',
  CONFIRM_SIGN_UP_LOST_CODE: 'Lost your code?',
  CONFIRM_SIGN_UP_RESEND_CODE: 'Resend Code',
  CONFIRM_SIGN_UP_SUBMIT_BUTTON_TEXT: 'Confirm',
  CONFIRM_SMS_CODE: 'Confirm SMS Code',
  CONFIRM_TOTP_CODE: 'Confirm TOTP Code',
  CONFIRM: 'Confirm',
  CREATE_ACCOUNT_TEXT: 'Create account',
  EMAIL_LABEL: 'Email Address *',
  EMAIL_PLACEHOLDER: 'Enter your email address',
  FORGOT_PASSWORD_TEXT: 'Forgot your password?',
  LESS_THAN_TWO_MFA_VALUES_MESSAGE: 'Less than two MFA types available',
  NEW_PASSWORD_LABEL: 'New password',
  NEW_PASSWORD_PLACEHOLDER: 'Enter your new password',
  NO_ACCOUNT_TEXT: 'No account?',
  USERNAME_REMOVE_WHITESPACE: 'Username cannot contain whitespace',
  PASSWORD_REMOVE_WHITESPACE: 'Password cannot start or end with whitespace',
  PASSWORD_LABEL: 'Password *',
  PASSWORD_PLACEHOLDER: 'Enter your password',
  PHONE_LABEL: 'Phone Number *',
  PHONE_PLACEHOLDER: '(555) 555-1212',
  QR_CODE_ALT: 'qrcode',
  RESET_PASSWORD_TEXT: 'Reset password',
  RESET_YOUR_PASSWORD: 'Reset your password',
  SELECT_MFA_TYPE_HEADER_TEXT: 'Select MFA Type',
  SELECT_MFA_TYPE_SUBMIT_BUTTON_TEXT: 'Verify',
  SEND_CODE: 'Send Code',
  SUBMIT: 'Submit',
  SETUP_TOTP_REQUIRED: 'TOTP needs to be configured',
  SIGN_IN_ACTION: 'Sign In',
  SIGN_IN_HEADER_TEXT: 'Sign in to your account',
  SIGN_IN_TEXT: 'Sign in',
  SIGN_IN_WITH_AMAZON: 'Sign in with Amazon',
  SIGN_IN_WITH_AUTH0: 'Sign in with Auth0',
  SIGN_IN_WITH_AWS: 'Sign in with AWS',
  SIGN_IN_WITH_FACEBOOK: 'Sign in with Facebook',
  SIGN_IN_WITH_GOOGLE: 'Sign in with Google',
  SIGN_OUT: 'Sign Out',
  SIGN_UP_EMAIL_PLACEHOLDER: 'Email',
  SIGN_UP_HAVE_ACCOUNT_TEXT: 'Have an account?',
  SIGN_UP_HEADER_TEXT: 'Create a new account',
  SIGN_UP_PASSWORD_PLACEHOLDER: 'Password',
  SIGN_UP_SUBMIT_BUTTON_TEXT: 'Create Account',
  SIGN_UP_USERNAME_PLACEHOLDER: 'Username',
  SUCCESS_MFA_TYPE: 'Success! Your MFA Type is now:',
  TOTP_HEADER_TEXT: 'Scan then enter verification code',
  TOTP_LABEL: 'Enter Security Code:',
  TOTP_ISSUER: 'AWSCognito',
  TOTP_SETUP_FAILURE: 'TOTP Setup has failed',
  TOTP_SUBMIT_BUTTON_TEXT: 'Verify Security Token',
  TOTP_SUCCESS_MESSAGE: 'Setup TOTP successfully!',
  UNABLE_TO_SETUP_MFA_AT_THIS_TIME: 'Failed! Unable to configure MFA at this time',
  USERNAME_LABEL: 'Username *',
  USERNAME_PLACEHOLDER: 'Enter your username',
  VERIFY_CONTACT_EMAIL_LABEL: 'Email',
  VERIFY_CONTACT_HEADER_TEXT: 'Account recovery requires verified contact information',
  VERIFY_CONTACT_PHONE_LABEL: 'Phone Number',
  VERIFY_CONTACT_SUBMIT_LABEL: 'Submit',
  VERIFY_CONTACT_VERIFY_LABEL: 'Verify',
  ADDRESS_LABEL: 'Address',
  ADDRESS_PLACEHOLDER: 'Enter your address',
  NICKNAME_LABEL: 'Nickname',
  NICKNAME_PLACEHOLDER: 'Enter your nickname',
  BIRTHDATE_LABEL: 'Birthday',
  BIRTHDATE_PLACEHOLDER: 'Enter your birthday',
  PICTURE_LABEL: 'Picture URL',
  PICTURE_PLACEHOLDER: 'Enter your picture URL',
  FAMILY_NAME_LABEL: 'Family Name',
  FAMILY_NAME_PLACEHOLDER: 'Enter your family name',
  PREFERRED_USERNAME_LABEL: 'Preferred Username',
  PREFERRED_USERNAME_PLACEHOLDER: 'Enter your preferred username',
  GENDER_LABEL: 'Gender',
  GENDER_PLACEHOLDER: 'Enter your gender',
  PROFILE_LABEL: 'Profile URL',
  PROFILE_PLACEHOLDER: 'Enter your profile URL',
  GIVEN_NAME_LABEL: 'First Name',
  GIVEN_NAME_PLACEHOLDER: 'Enter your first name',
  ZONEINFO_LABEL: 'Time zone',
  ZONEINFO_PLACEHOLDER: 'Enter your time zone',
  LOCALE_LABEL: 'Locale',
  LOCALE_PLACEHOLDER: 'Enter your locale',
  UPDATED_AT_LABEL: 'Updated At',
  UPDATED_AT_PLACEHOLDER: 'Enter the time the information was last updated',
  MIDDLE_NAME_LABEL: 'Middle Name',
  MIDDLE_NAME_PLACEHOLDER: 'Enter your middle name',
  WEBSITE_LABEL: 'Website',
  WEBSITE_PLACEHOLDER: 'Enter your website',
  NAME_LABEL: 'Full Name',
  NAME_PLACEHOLDER: 'Enter your full name',
  PHOTO_PICKER_TITLE: 'Picker Title',
  PHOTO_PICKER_HINT: 'Ancillary text or content may occupy this space here',
  PHOTO_PICKER_PLACEHOLDER_HINT: 'Placeholder hint',
  PHOTO_PICKER_BUTTON_TEXT: 'Button',
  IMAGE_PICKER_TITLE: 'Add Profile Photo',
  IMAGE_PICKER_HINT: 'Preview the image before upload',
  IMAGE_PICKER_PLACEHOLDER_HINT: 'Tap to image select',
  IMAGE_PICKER_BUTTON_TEXT: 'Upload',
  PICKER_TEXT: 'Pick a file',
  TEXT_FALLBACK_CONTENT: 'Fallback Content',
  CONFIRM_SIGN_UP_FAILED: 'Confirm Sign Up Failed',
  SIGN_UP_FAILED: 'Sign Up Failed',
} as const;

const InteractionsStrings = {
  CHATBOT_TITLE: 'ChatBot Lex',
  TEXT_INPUT_PLACEHOLDER: 'Write a message',
  VOICE_INPUT_PLACEHOLDER: 'Click mic to speak',
  CHAT_DISABLED_ERROR: 'Error: Either voice or text must be enabled for the chatbot',
  NO_BOT_NAME_ERROR: 'Error: Bot name must be provided to ChatBot',
} as const;

const AuthErrorStrings = {
  DEFAULT_MSG: 'Authentication Error',
  EMPTY_USERNAME: 'Username cannot be empty',
  INVALID_USERNAME: 'The username should either be a string or one of the sign in types',
  EMPTY_PASSWORD: 'Password cannot be empty',
  EMPTY_CODE: 'Confirmation code cannot be empty',
  SIGN_UP_ERROR: 'Error creating account',
  NO_MFA: 'No valid MFA method provided',
  INVALID_MFA: 'Invalid MFA type',
  EMPTY_CHALLENGE: 'Challenge response cannot be empty',
  NO_USER_SESSION: 'Failed to get the session because the user is empty',
} as const;

type AuthStrings = typeof AuthStrings[keyof typeof AuthStrings];
type InteractionsStrings = typeof InteractionsStrings[keyof typeof InteractionsStrings];
type AuthErrorStrings = typeof AuthErrorStrings[keyof typeof AuthErrorStrings];

export const amplifyVocabularies = {
  ja: {
    [AuthStrings.BACK_TO_SIGN_IN]: 'サインインに戻る',
    [AuthStrings.CHANGE_PASSWORD_ACTION]: '変更',
    [AuthStrings.CHANGE_PASSWORD]: 'パスワードの変更',
    [AuthStrings.CODE_LABEL]: '検証コード',
    [AuthStrings.CODE_PLACEHOLDER]: 'コードを入力してください',
    [AuthStrings.CONFIRM_SIGN_UP_CODE_LABEL]: '確認コード',
    [AuthStrings.CONFIRM_SIGN_UP_CODE_PLACEHOLDER]: 'コードを入力してください',
    [AuthStrings.CONFIRM_SIGN_UP_HEADER_TEXT]: 'サインアップの確認',
    [AuthStrings.CONFIRM_SIGN_UP_LOST_CODE]: 'コードを紛失しましたか?',
    [AuthStrings.CONFIRM_SIGN_UP_RESEND_CODE]: 'コードを再送',
    [AuthStrings.CONFIRM_SIGN_UP_SUBMIT_BUTTON_TEXT]: '確認',
    [AuthStrings.CONFIRM_SMS_CODE]: 'SMSコードの確認',
    [AuthStrings.CONFIRM_TOTP_CODE]: 'TOTPコードの確認',
    [AuthStrings.CONFIRM]: '確認',
    [AuthStrings.CREATE_ACCOUNT_TEXT]: 'アカウントを作成',
    [AuthStrings.EMAIL_LABEL]: 'メールアドレス*',
    [AuthStrings.EMAIL_PLACEHOLDER]: 'メールアドレスを入力してください',
    [AuthStrings.FORGOT_PASSWORD_TEXT]: 'パスワードを忘れましたか?',
    [AuthStrings.LESS_THAN_TWO_MFA_VALUES_MESSAGE]: '使用可能なMFAタイプが2つ未満',
    [AuthStrings.NEW_PASSWORD_LABEL]: '新しいパスワード',
    [AuthStrings.NEW_PASSWORD_PLACEHOLDER]: '新しいパスワードを入力してください',
    [AuthStrings.NO_ACCOUNT_TEXT]: 'アカウントがありませんか?',
    [AuthStrings.USERNAME_REMOVE_WHITESPACE]: 'ユーザー名に空白を含めることはできません',
    [AuthStrings.PASSWORD_REMOVE_WHITESPACE]: 'パスワードは空白で開始または終了できません',
    [AuthStrings.PASSWORD_LABEL]: 'パスワード*',
    [AuthStrings.PASSWORD_PLACEHOLDER]: 'パスワードを入力してください',
    [AuthStrings.PHONE_LABEL]: '電話番号*',
    [AuthStrings.PHONE_PLACEHOLDER]: '(555)555-1212',
    [AuthStrings.QR_CODE_ALT]: 'qrcode',
    [AuthStrings.RESET_PASSWORD_TEXT]: 'パスワードをリセット',
    [AuthStrings.RESET_YOUR_PASSWORD]: 'パスワードをリセットします',
    [AuthStrings.SELECT_MFA_TYPE_HEADER_TEXT]: 'MFAタイプを選択',
    [AuthStrings.SELECT_MFA_TYPE_SUBMIT_BUTTON_TEXT]: '検証',
    [AuthStrings.SEND_CODE]: 'コードを送信',
    [AuthStrings.CONFIRM]: '送信',
    [AuthStrings.SETUP_TOTP_REQUIRED]: 'TOTPを構成する必要があります',
    [AuthStrings.SIGN_IN_ACTION]: 'サインイン',
    [AuthStrings.SIGN_IN_HEADER_TEXT]: 'アカウントにサインインします',
    [AuthStrings.SIGN_IN_TEXT]: 'サインイン',
    [AuthStrings.SIGN_IN_WITH_AMAZON]: 'Amazonでサインイン',
    [AuthStrings.SIGN_IN_WITH_AUTH0]: 'Auth0でサインイン',
    [AuthStrings.SIGN_IN_WITH_AWS]: 'AWSでサインイン',
    [AuthStrings.SIGN_IN_WITH_FACEBOOK]: 'Facebookでサインイン',
    [AuthStrings.SIGN_IN_WITH_GOOGLE]: 'Googleでサインイン',
    [AuthStrings.SIGN_OUT]: 'サインアウト',
    [AuthStrings.SIGN_UP_EMAIL_PLACEHOLDER]: 'Eメール',
    [AuthStrings.SIGN_UP_HAVE_ACCOUNT_TEXT]: 'アカウントをお持ちですか?',
    [AuthStrings.SIGN_UP_HEADER_TEXT]: '新しいアカウントを作成します',
    [AuthStrings.SIGN_UP_PASSWORD_PLACEHOLDER]: 'パスワード',
    [AuthStrings.SIGN_UP_SUBMIT_BUTTON_TEXT]: 'アカウントの作成',
    [AuthStrings.SIGN_UP_USERNAME_PLACEHOLDER]: 'ユーザー名',
    [AuthStrings.SUCCESS_MFA_TYPE]: '成功! MFAタイプは次のようになります]: ',
    [AuthStrings.TOTP_HEADER_TEXT]: 'スキャンして確認コードを入力',
    [AuthStrings.TOTP_LABEL]: 'セキュリティコードを入力してください:',
    [AuthStrings.TOTP_ISSUER]: 'AWSCognito',
    [AuthStrings.TOTP_SETUP_FAILURE]: 'TOTPセットアップが失敗しました',
    [AuthStrings.TOTP_SUBMIT_BUTTON_TEXT]: 'セキュリティトークンの確認',
    [AuthStrings.TOTP_SUCCESS_MESSAGE]: 'TOTPを正常にセットアップします!',
    [AuthStrings.UNABLE_TO_SETUP_MFA_AT_THIS_TIME]: '失敗しました!現時点ではMFAを構成できません',
    [AuthStrings.USERNAME_LABEL]: 'ユーザー名*',
    [AuthStrings.USERNAME_PLACEHOLDER]: 'ユーザー名を入力してください',
    [AuthStrings.VERIFY_CONTACT_EMAIL_LABEL]: 'Eメール',
    [AuthStrings.VERIFY_CONTACT_HEADER_TEXT]: 'アカウントの回復には確認済みの連絡先情報が必要です',
    [AuthStrings.VERIFY_CONTACT_PHONE_LABEL]: '電話番号',
    [AuthStrings.VERIFY_CONTACT_SUBMIT_LABEL]: '送信',
    [AuthStrings.VERIFY_CONTACT_VERIFY_LABEL]: '検証',
    [AuthStrings.ADDRESS_LABEL]: 'アドレス',
    [AuthStrings.ADDRESS_PLACEHOLDER]: '住所を入力してください',
    [AuthStrings.NICKNAME_LABEL]: 'ニックネーム',
    [AuthStrings.NICKNAME_PLACEHOLDER]: 'ニックネームを入力してください',
    [AuthStrings.BIRTHDATE_LABEL]: '誕生日',
    [AuthStrings.BIRTHDATE_PLACEHOLDER]: '誕生日を入力してください',
    [AuthStrings.PICTURE_LABEL]: '画像のURL',
    [AuthStrings.PICTURE_PLACEHOLDER]: '画像のURLを入力してください',
    [AuthStrings.FAMILY_NAME_LABEL]: '家族名',
    [AuthStrings.FAMILY_NAME_PLACEHOLDER]: '家族の名前を入力してください',
    [AuthStrings.PREFERRED_USERNAME_LABEL]: '優先ユーザー名',
    [AuthStrings.PREFERRED_USERNAME_PLACEHOLDER]: '希望するユーザー名を入力してください',
    [AuthStrings.GENDER_LABEL]: '性別',
    [AuthStrings.GENDER_PLACEHOLDER]: '性別を入力してください',
    [AuthStrings.PROFILE_LABEL]: 'プロファイルURL',
    [AuthStrings.PROFILE_PLACEHOLDER]: 'プロファイルURLを入力してください',
    [AuthStrings.GIVEN_NAME_LABEL]: '',
    [AuthStrings.GIVEN_NAME_PLACEHOLDER]: '名を入力してください',
    [AuthStrings.ZONEINFO_LABEL]: 'タイムゾーン',
    [AuthStrings.ZONEINFO_PLACEHOLDER]: 'タイムゾーンを入力してください',
    [AuthStrings.LOCALE_LABEL]: 'ロケール',
    [AuthStrings.LOCALE_PLACEHOLDER]: 'ロケールを入力してください',
    [AuthStrings.UPDATED_AT_LABEL]: '更新場所',
    [AuthStrings.UPDATED_AT_PLACEHOLDER]: '情報が最後に更新された時刻を入力してください',
    [AuthStrings.MIDDLE_NAME_LABEL]: 'ミドルネーム',
    [AuthStrings.MIDDLE_NAME_PLACEHOLDER]: 'ミドルネームを入力してください',
    [AuthStrings.WEBSITE_LABEL]: 'ウェブサイト',
    [AuthStrings.WEBSITE_PLACEHOLDER]: 'あなたのウェブサイトを入力してください',
    [AuthStrings.NAME_LABEL]: 'フルネーム',
    [AuthStrings.NAME_PLACEHOLDER]: 'フルネームを入力してください',
    [AuthStrings.PHOTO_PICKER_TITLE]: 'ピッカータイトル',
    [AuthStrings.PHOTO_PICKER_HINT]: '補助テキストまたはコンテンツがここのこのスペースを占める場合があります',
    [AuthStrings.PHOTO_PICKER_PLACEHOLDER_HINT]: 'プレースホルダーヒント',
    [AuthStrings.PHOTO_PICKER_BUTTON_TEXT]: 'ボタン',
    [AuthStrings.IMAGE_PICKER_TITLE]: 'プロフィール写真を追加',
    [AuthStrings.IMAGE_PICKER_HINT]: 'アップロードする前に画像をプレビューします',
    [AuthStrings.IMAGE_PICKER_PLACEHOLDER_HINT]: 'タップして画像を選択',
    [AuthStrings.IMAGE_PICKER_BUTTON_TEXT]: 'アップロード',
    [AuthStrings.PICKER_TEXT]: 'ファイルを選択',
    [AuthStrings.TEXT_FALLBACK_CONTENT]: 'フォールバックコンテンツ',
    [AuthStrings.CONFIRM_SIGN_UP_FAILED]: 'サインアップが失敗したことを確認します',
    [AuthStrings.SIGN_UP_FAILED]: 'サインアップに失敗しました',
    [InteractionsStrings.CHATBOT_TITLE]: 'ChatBot Lex',
    [InteractionsStrings.TEXT_INPUT_PLACEHOLDER]: 'メッセージを書く',
    [InteractionsStrings.VOICE_INPUT_PLACEHOLDER]: 'マイクをクリックして話します',
    [InteractionsStrings.CHAT_DISABLED_ERROR]:
      'エラー:チャットボットに対して音声またはテキストのいずれかを有効にする必要があります',
    [InteractionsStrings.NO_BOT_NAME_ERROR]: 'エラー:ボット名をChatBotに提供する必要があります',
    [AuthErrorStrings.DEFAULT_MSG]: '認証エラー',
    [AuthErrorStrings.EMPTY_USERNAME]: 'ユーザー名を空にすることはできません',
    [AuthErrorStrings.INVALID_USERNAME]: 'ユーザー名は文字列またはサインインタイプのいずれかである必要があります',
    [AuthErrorStrings.EMPTY_PASSWORD]: 'パスワードを空にすることはできません',
    [AuthErrorStrings.EMPTY_CODE]: '確認コードを空にすることはできません',
    [AuthErrorStrings.SIGN_UP_ERROR]: 'アカウントの作成中にエラーが発生しました',
    [AuthErrorStrings.NO_MFA]: '有効なMFAメソッドが提供されていません',
    [AuthErrorStrings.INVALID_MFA]: '無効なMFAタイプ',
    [AuthErrorStrings.EMPTY_CHALLENGE]: 'チャレンジレスポンスを空にすることはできません',
    [AuthErrorStrings.NO_USER_SESSION]: 'ユーザーが空であるため、セッションを取得できませんでした',
    'Select Language': '言語選択',
  },
};


課題

  • Cognitoのエラーに関しては、多言語化できていない。
    • エラー一覧があれば、言語用constに追加するだけでよいが見つからなかった。
  • 言語切り替えにページリロードをしている。

参考

https://docs.amplify.aws/lib/utilities/i18n/q/platform/js
https://github.com/aws-amplify/amplify-js/blob/main/packages/amplify-ui-components/src/common/Translations.ts

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

AWSの障害を考える

2021/02/19(土)の夜間にAWSで障害が発生しました
ここのところ一年に一度のペースで大きな障害が発生しているので
自分なりに考察していきます

2021/2/19日のAWS障害の詳細

  • 障害時間 2021/02/19(土) 23:50~20(日) AM5:30
  • ダウンタイム 4時間40分
  • 障害が発生したリージョン/AZ 

    • 東京リージョン
    • ap-northeast-1(apne1-az1)
  • 影響を受けたサービス

    • EC2
    • EBS
  • 原因

    • 冷却ユニットの温度上昇の影響により、冷却ユニットの電源がダウンする

過去に発生した大きな障害(東京リージョン)

  • 2019/8/23 EC2、EBS、RDS、Redshift、ElastiCache 、Workspacesで障害

  • 2020/4/20 SQS、Lambda、CloudWatch、CloudFormationで障害

    • ダウンタイム 約4時間
      ※レポート見つからず
  • ちなみに2013年~2019年まで東京リージョンで大きな障害は無かったのである意味凄いなという感じです
    反対にユーザは「東京リージョンは落ちない」という錯覚をしていた人がほとんどだと思います(海外リージョンはそれなりに障害が起きています)

責任共有モデル

AWSには責任共有モデルがあり、AWSの責任とユーザの責任の境界線を明示的にしています
例えば、データの保護やバックアップは「ユーザ側でやってくださいね、AWSは責任を取りませんよ」というものです

image.png

Design for Failure(障害設計)

AWSは障害を見据えて設計してくださいと提唱しています
従いユーザは障害が発生することを考えての設計指針が必要です

SLAの条件

  • EC2、EBSのSLA 99.99% (月間ダウンタイム 4.32 分)
  • SLA適用条件(EC2、EBS)
    image.png

    • 要約すると東京リージョンの場合は2つのAZで接続不可にならないと適用されない
  • 私の記憶ではAZが2つ同時に接続不可になったことは無いです

  • Amazon Compute サービスレベルアグリーメント

すべての障害対策は不可能

すべての障害対策をすることは不可能です
障害とは予測できない事が多いし、予期できないことが起こるから世の中でシステム障害が起きています

予測できない障害に対してユーザ側は適材適所柔軟に対応するしか無いと考えます
ただし、予測の範囲内で障害設計することが大事です
万全な準備しておけば、障害への対応時間を短くできます

完全な障害設計は不可能

完全な障害設計は不可能です
そもそも、AWSはサービスのアーキテクチャを開示していません
これが何を意味するかと言うと、アーキテクチャが分からないユーザは
分からない部分の対処をできるはずがないです(障害設計できない)

例えばですが、AというAWSのサービスがあったとします
このAサービスがEC2のアプライアンス上で動いているとしたら
Aサービスの障害設計をしたとしても、EC2がダウン→Aサービスもダウンします

故に完全な障害設計は不可能だと考えています
ユーザはできるだけの障害設計をする、障害に備えることしかできません

実際の障害対応は想像より複雑

障害テストは割と0と1で考えている事が多いです
例えば「EC2がダウン→切替える」とかです

こういう障害が起こってくれれば対応する側も助かるのですが
実際の障害は部分部分でゆっくり少しづつ死んでいくという事があります

EC2のCPUの負荷が少しづつ高くなる、EBSのパフォーマンスが低下していくなどで
結果的にサービスが不安定になるという、対応する側にとっては判断も難しく
複雑で嫌なパターンになります

AWSの障害復旧は早い

毎回数時間で障害復旧します
ダウンしたのは事実ですが、それを数時間で復旧までもっていくAWSは凄いなと思います
実際データセンタで障害が起きたら、このスピード感で対応ができるかと言うと、、、
正直難しいかなと思います

実態を歪めない

バイアスやハロー効果で実態を歪めないでください

メガクラウドで障害起きると大きなニュースになります
どうしても印象が悪くなったり、クラウドはダメだってなりがちです

例えば交通事故で言うと、飛行機の事故と、個人車の事故を比べるようなものです
実態としては、飛行機事故より個人車の事故のほうが圧倒的に多いです

さいごに障害に対する考え

2020年に東証一部のシステムでも障害はありましたが、障害を防ぐことは不可能です
人間に体調を崩すなと言っているのと一緒です
システムはダウンする事が前提です

ただ、障害に備えることは可能なので、RTO/RPOを考慮して障害設計をする、障害時のオペレーションを確立しておく事が重要です

前述に書いた「Well-Arc」を参考にするのも一つの手段です
AWSの10年以上の設計指針が積み上げられています
Well-Arcをすべて実行するのは不可能かもしれませんが参考にはなります
Well-Arcはこちら

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

【AWS】VPCを作成

はじめに

Amazon VPC (Virtual Private Cloud)の略で、AWS上に自身のプライベートネットワークを作成できます。
このネットワーク上にEC2やRDSといったAWSサービスを作成しシステムを構築できます。

プライベートなNW空間になりますが、インターネットゲートウェイを作成しインターネットと接続したり、Direct Connectという機能を利用して社内のイントラと接続することも可能です。

前提

VPC作成前にリージョンとアベイラビリティゾーン(AZ)について簡単に触れておきます。
AWSはリージョンとAZという単位に分割して管理されています。

リージョン

AWSの各サービスが提供されている世界各地の地域のことです。
例えば、日本はアジアパシフィック(東京)というリージョンに含まれています。他にもバージニア北部、オハイオといったリージョンが存在、リージョン間は完全に分離されており耐障害性を保っています。

アベイラビリティゾーン(AZ)

AZとは、各リージョンに存在するデータセンターです。 各リージョンには複数のアベイラビリティゾーンが存在し、それぞれにサブネットやEC2といったサービスを配置することで冗長性を保つことができます。

今回作成するVPCの最終構成

今回はアジアパシフィック(東京)リージョンに以下のようなVPCを作成します。
インターネットと通信ができるパブリックサブネット、インターネットとの通信ができず、VPC内のみの通信が可能なプライベートサブネットを、各AZ内に作成します。
VPC-20.png

VPCの作成

VPCを作成していきます。
サービスからVPCを選択後、「VPCを作成」をクリック
VPC-10.png
VPCの設定
IPv4 CIDR ブロックでプライベートIPアドレスの範囲を指定します。
CIDRブロックサイズは/16~/28のサイズを指定可能で、
スラッシュの後の数字が小さいほど、割り振る IP アドレスの数が増えます。(/16だと65,536 個分、/28だと11個分)
IPv4 CIDR ブロックは本来、必要なIPアドレスを見積もりした上で設計しますが、今回は以下の通り設定します。

名前タグ: rails_test
IPv4 CIDR ブロック: 10.0.0.0/16

VPC-1.png
VPCが作成できました。
VPC-23.png

サブネットの設定

サブネットは簡単にいうとVPCを細かく区切ったネットワーク空間です。
先ほど作成したVPCの中にサブネットを構築していきます。
サブネットマスク(16bit ~ 28bit)で割り当て空間の設定や、
配置するAZを指定します。

左ペインのサブネットを選択後、「サブネット作成」ボタンをクリック。
VPC-11.png
VPC IDで作成したVPCを選択
VPC-2.png

下の図は以下の設定をしたものです。

サブネット名: Public-subnet-a
アベイラビリティーゾーン: ap-northeast-1a
IPv4 CIDR ブロック: 10.0.1.0/24

VPC-3.png

同様の手順で他3つのサブネットも作成します。

サブネット名: Public-subnet-c
アベイラビリティーゾーン: ap-northeast-1c
IPv4 CIDR ブロック: 10.0.2.0/24
サブネット名: Private-subnet-a
アベイラビリティーゾーン: ap-northeast-1a
IPv4 CIDR ブロック: 10.0.11.0/24
サブネット名: Private-subnet-c
アベイラビリティーゾーン: ap-northeast-1c
IPv4 CIDR ブロック: 10.0.12.0/24

サブネットの作成は完了です。
VPC-22.png

インターネットゲートウェイの作成

VPCを作成しましたが、このままではインターネットに接続できません。
インターネットにはインターネットゲートウェイ(IGW)を経由して通信する必要があります。
まずはIGWを作成します。

左ペインのインターネットゲートウェイを選択後、「インターネットゲートウェイの作成」をクリック
VPC-12.png
以下の通り設定

名前タグ:igw-rails-test

VPC-4.png
作成直後はデタッチ状態のため、先ほど作成したVPCにアタッチを行います。
作成したインターネットゲートウェイの「アクション」よりVPCにアタッチをクリック
VPC-13.png
使用可能なVPCで作成したVPCを選択
VPC-5.png
IGWの作成、アタッチは完了です。
VPC-21.png

ルートテーブルの設定

ルートテーブルとは通信経路を決めたルートと呼ばれるルールを定義されたテーブル情報のことです。
IGWをアタッチしましたが、まだインターネットと通信することはできません。ルートテーブルにIGWへのルートを追加しインターネット接続できるようにします。

左ペインのルートテーブルを選択、「ルートテーブルの作成」をクリック

VPC-14.png
下の図は以下の設定をしたものです。

名前タグ: Public-Route-Table
VPC: (作成したVPCを指定)

VPC-15.png

ルートの編集

ルートテーブル作成後、ルートタブの「ルートの編集」をクリック

VPC-16.png
IGWへのルートテーブルを追加します。

送信先: 0.0.0.0/0
ターゲット: (アタッチしたIGW)

VPC-7.png

※送信先の0.0.0.0/0は全てのアドレスとマッチするという意味です。全ての送信先に対してIGWを経由するという設定になります。
またルートテーブルにはlocalというテーブルがはじめから関連づけられています。これはVPC内のIPアドレスに対してのルート情報です。
先ほど、全ての送信先に対してIGWを経由と書きましたが、
ルートテーブルは最長プレフィックス一致(定義された最も具体的なルートが優先)が適用されるため、送信先がVPC内のIPアドレスの場合はlocal、それ以外はIGWを経由して通信することになります。

サブネットの関連付け

作成したルートテーブルにサブネットを割り当てします。
サブネットの関連付けタブを選択後、「サブネットの関連付けの編集」ボタンをクリック

VPC-17.png
下の図は以下の設定をしたものです。

サブネットID: Public-subnet-a、Public-subnet-c

VPC-18.png

ここで設定したサブネットが、パブリックサブネット(インターネットへのアクセスが可能)になります。

省略していますが、ターゲットがlocalのみ(IGWのルート情報がない)のルートテーブルを作成し、関連付けしたサブネットがプライベートサブネットとなります。

完成

VPC-20.png

最後に

今回は一般的なVPC作成について投稿しました。
作成したパブリックサブネットにEC2、プライベートサブネットにRDS等を配置しWebアプリケーションを構築することができます。
VPCには他にもネットワークACLで通信を制御したり等、さまざまな機能があるため、どんどん活用していきたいです。

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

【学習メモ】AWS DynamoDB

DynamoDbとは

Amazonが提供するNoSQLデータベースサービス。
特徴:信頼性が高い、スケーラビリテ、低レイテンシ

DynamoDbの基本概念

DynamoDbの構成

構想部分:テーブル、アイテム、属性。
DynamoDbはテーブル単位から設定する。
テーブル:データのコレクションのこと。
アイテム:各テーブルの中にアイテムを作ってデータを作成する。
属性:各アイテムは1つ以上の属性で構成される。
dynamoDBのテーブル.png

DynamoDbのプライマリーキー

テーブルを作成するとき、テーブル名を指定する以外、プライマリーキーも指定しないといけいない。
プライマリーキーはアイテムを認識するものなので、異なるアイテムは同じプライマリーキーを使っては行けない。

プライマリーキーは2つある。
・パーティションキー(Partition Key)
・パーティションキー & ソートキー (Partition Key+Sort Key)⇨複合キーも呼ばれる。
 ※第一属性はパーティションキー、第二属性はソートキー。パーティションキーはストレージの場所を決まったので、ソートキーの重複は許さない。

DynamoDb Secondary Index

プライマリーキー以外、セカンダリーインデックスを使って、データの検索も可能。
一つのテーブルは一つor複数のセカンダリーインデックスを作れる。

DynamoDb Secondary Index
・Global secondary index(GSI)
・Local secondary index(LSI)

★GSI
テーブルのパーティションキー、ソートキーと異なるインデックス。

★LSI
テーブルのパーティションキーは一緒で、ソートキーは異なるインデックス。

一つのテーブルは最大五つのGSI、五つのLSIを設定できる。

DynamoDB APIs

®︎Reference:https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/HowItWorks.API.html

dynamo table APIs.png
dynamo control plane API.png

DynamoDB Streams

DynamoDB テーブルに保存された項目の追加・変更・削除の発生時の履歴をキャプチャできる機能。
※過去24時間以内のデータ変更の履歴を保存し、24時間を経過すると消去される。

DynamoDbの使いところ

IoT:
・デバイスデータの保存。
・DynamoDbをAmazon Redshiftにデータウェアハウスに接続し、BI分析を実施。

ゲーム:
・ゲームの行動記録。

Advertisement Serving

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

Amazon Aurora Serverless

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

AWSとWordPressを使ってWebサイトを構築しよう①

本記事ではパート毎に分けて最終的にはタイトルの通り「AWSとWordpressを使ってWebサイトを構築」していきます。 初学者の方はゆっくりと確実に1つ1つ記事の通りに進めて頂けたら最終的にはWebサイトを構築することができます。 まずはWebサイト構築を始める前に理解しておくべきサーバーとネットワークについて抑えましょう。 目次 1.サーバー構築の考え方 2.ネットワーク構築の考え方 1. サーバー構築の考え方 そもそもサーバーとは簡単に言うと「何かを相手に提供する」ということです。 例えばコーヒーサーバーでしたらコーヒーを提供するためのサーバー。 それと同じようにコンピュータで言うサーバーとはクライアントにサービスを提供するためのサーバーです。 サーバーを構築する際にまずは”どんなサーバーが必要なのか”を考えます。 もしあなたが”自分のWebサイトを作成して公開したい”という時は、Webサイトを提供するためのWebサーバーが必要となります。 またWebサイトに書いた記事や画像を保存しておくためのデータベースも必要ですよね。なのでDBサーバーも必要です。 このようにどんなサービスを提供するかで構築するサーバーも変わっていきます。 とはいえWebサーバーだからWebサーバーというものがあるということではなく、正確に言うと「Windows Server」などのOSをインストールしたコンピュータにWeb機能をインストールさせたものをWebサーバーと言います。 2. ネットワーク構築の考え方 サーバーを構築した後にインターネットへ繋げるにはネットワークを通じて通信できるようにしてあげる必要があります。 インターネットはTCP/IPというプロトコルを使ってネットワーク上で通信を行っており、それぞれのサーバーにIPアドレス(こんな形「10.0.1.1」)という固有の番号を割り振って他の機器と重複しないように識別しています。 さらにインターネットへ接続するのにルーターという機器を使用しており、サーバーからルーターへ通信するように構成する必要があります。 また通常インターネット上ではWebサイトの住所を表す時に「https://www.google.co.jp」 このようなドメイン名と呼ばれるものを使用します。 実はこれは人間が見やすいように作成されたもので、IPアドレスに関連付けされて設定されています。 このような設定を行うのにDNSサーバーというサーバーの設定も必要です。 それでは次回からAmazonが提供しているクラウドサービスであるAWSサービスにWordPressをインストールしてWebサイトを構築していきましょう! 次の記事はこちらから AWSとWordPressを使ってWebサイトを構築しよう②
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

①NiceHashマイニング収益をAWS Lambda×SNSでメール通知する

目次

1. 背景

 日々メインPCとは別にマイニングリグを稼働させて、NiceHashでマイニングをしているのですが、残高確認する際にNiceHashのダッシュボードを都度確認するのは面倒なので日次ジョブとして通知したいと思ってました。
 業務ではコーディングや、AWSに触れる機会が一切ないので、勉強がてらAWS上でスマホへメール通知するシステムを構築してみました。
 最重要なマイニングリグのヘルスチェックエラー通知はMySettingsから設定できる 1 仕様なので、今回は収益情報のみをメールの通知対象としました。

2. 構成/構築手順

システム構成は、AWS Lambdaを中心とした基本的なサーバレスアーキテクチャです。

処理の流れ
 1. EventBridge(CroudWatch Event)の日次実行cronがトリガーとなり、Lambda関数をキック
 2. Lambdaでは、外部APIからマイニング収益情報を取得
 3. S3バケットへ残高情報を書き込み、前日の残高情報を取得、残高の増減を算出
 4. Amazon SNSへ値をpublishしてスマホへメールを通知
Qiita 掲載図①.png

2-1.Lambdaの構築

2-1-1.IAMロールの作成

AWSサービス間を連携するために新規IAMロールを作成し必要なポリシーをアタッチする
・IAMを起動し、ユースケースLambdaを選択し「次のステップ」をクリック
image.png
・S3バケットへ残高情報を読み書きするためにAmazonS3FullAccess、収益情報をメール通知するためにAmazonSNSFullAccessポリシーをロールにアタッチし「次のステップ」をクリック
image.png
・タグの追加は不要なので何も記入せず「次のステップ」をクリック
・ロール名は適当にNiceHash-Nortificationとして「ロールの作成」をクリック
image.png

2-1-2.Lambda関数の作成

呼び出されるLambda関数本体を作成する
・サービスからLambdaを起動し、以下のように入力し「関数の作成」をクリック

 関数名:「NiceHash-Nortification-Mail」
 ランタイム:「Python 3.6」#Python3系ならたぶんOK
 アクセス権限:「NiceHash-Nortification」#作成したIAMロール

image.png

2-1-3.ソースコードのデプロイ

Lambdaで実行するプログラムをデプロイする
・以下4つのpythonファイルを新規に作成してコードをデプロイ

NiceHash-Nortification-Mail
NiceHash-Nortification-Mail/
├ lambda_function.py
├ nicehash.py
├ marketrate.py
└ s3inout.py

Lambdaで呼び出されるメインプログラム

lambda_function.py
import json
import datetime
import boto3

import nicehash
import marketrate
import s3inout
#Function kicked by AWS Lambda
def lambda_handler(event, context):
    client = boto3.client('sns')
    #Amazon SNS
    TOPIC_ARN = 'arn:aws:sns:xx-xxxx-x:xxxxxxxxxx:NiceHashSNS' # SNSのARNを指定
    msg = create_message()
    subject = "NiceHash-Mining 日次収益通知"
    #Send a notification message to Amazon SNS
    response = client.publish(
        TopicArn = TOPIC_ARN,
        Message = msg,
        Subject = subject
    )
#Function to get a nortification message
def create_message():
    #NiceHash API    
    host = 'https://api2.nicehash.com'
    organisation_id = 'xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx' # hogehoge
    key = 'xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx' # API Key Code
    secret = 'xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx' # API Secret Key Code
    market='BTC'

    #S3 bucket
    bucket_name = '[bucket_name]'#hogehoge
    key_name = '[balance_filename]'#hogehoge

    #Get mining information from NiceHash API
    PrivateApi = nicehash.private_api(host, organisation_id, key, secret)
    accounts_info = PrivateApi.get_accounts_for_currency(market)
    balance_row = float(accounts_info['totalBalance'])

    #Get currency_to_JPY_rate from CoinGecko API
    TradeTable = marketrate.trade_table(market)
    rate = TradeTable.get_rate()
    balance_jpy = int(balance_row*rate)

    #S3 dealer
    S3dealer = s3inout.s3_dealer(bucket = bucket_name, key = key_name)
    pre_balance = int(S3dealer.read_from_s3_bucket())
    diff = balance_jpy - pre_balance
    S3dealer.write_to_s3_bucket(str(balance_jpy))

    #Nortification message
    time_text = "時刻: " + str(datetime.datetime.now(datetime.timezone(datetime.timedelta(hours=9))))[:19]
    market_text = "仮想通貨: " + market
    rate_text = "単位仮想通貨価値: " + str(rate) + "円"
    balance_text = "現在の残高: " + str(balance_jpy) + "円"
    pre_balance_text = "昨日の残高: " + str(pre_balance) + "円"
    symbol = "+" if diff > 0 else ""
    diff_txt = "【日次収益: " + str(symbol) + str(diff) + "円】"
    mon_revenue = "推定月次収益: " + str(diff*30) + "円"
    ann_revenue = "推定年次収益: " + str(diff*365) + "円"
    msg = '\n'.join([time_text,market_text,rate_text,balance_text,pre_balance_text,diff_txt,mon_revenue,ann_revenue])
    return msg

NiceHash API (2-2で説明)

nicehash.py
from datetime import datetime
from time import mktime
import uuid
import hmac
import requests
import json
from hashlib import sha256
import optparse
import sys
class private_api:
  def __init__(self, host, organisation_id, key, secret, verbose=False):
      self.key = key
      self.secret = secret
      self.organisation_id = organisation_id
      self.host = host
      self.verbose = verbose
  def request(self, method, path, query, body):
      xtime = self.get_epoch_ms_from_now()
      xnonce = str(uuid.uuid4())
      message = bytearray(self.key, 'utf-8')
      message += bytearray('\x00', 'utf-8')
      message += bytearray(str(xtime), 'utf-8')
      message += bytearray('\x00', 'utf-8')
      message += bytearray(xnonce, 'utf-8')
      message += bytearray('\x00', 'utf-8')
      message += bytearray('\x00', 'utf-8')
      message += bytearray(self.organisation_id, 'utf-8')
      message += bytearray('\x00', 'utf-8')
      message += bytearray('\x00', 'utf-8')
      message += bytearray(method, 'utf-8')
      message += bytearray('\x00', 'utf-8')
      message += bytearray(path, 'utf-8')
      message += bytearray('\x00', 'utf-8')
      message += bytearray(query, 'utf-8')
      if body:
          body_json = json.dumps(body)
          message += bytearray('\x00', 'utf-8')
          message += bytearray(body_json, 'utf-8')
      digest = hmac.new(bytearray(self.secret, 'utf-8'), message, sha256).hexdigest()
      xauth = self.key + ":" + digest
      headers = {
          'X-Time': str(xtime),
          'X-Nonce': xnonce,
          'X-Auth': xauth,
          'Content-Type': 'application/json',
          'X-Organization-Id': self.organisation_id,
          'X-Request-Id': str(uuid.uuid4())
      }
      s = requests.Session()
      s.headers = headers
      url = self.host + path
      if query:
          url += '?' + query
      if self.verbose:
          print(method, url)
      if body:
          response = s.request(method, url, data=body_json)
      else:
          response = s.request(method, url)
      if response.status_code == 200:
          return response.json()
      elif response.content:
          raise Exception(str(response.status_code) + ": " + response.reason + ": " + str(response.content))
      else:
          raise Exception(str(response.status_code) + ": " + response.reason)

  def get_epoch_ms_from_now(self):
      now = datetime.now()
      now_ec_since_epoch = mktime(now.timetuple()) + now.microsecond / 1000000.0
      return int(now_ec_since_epoch * 1000)

  def algo_settings_from_response(self, algorithm, algo_response):
      algo_setting = None

      for item in algo_response['miningAlgorithms']:
          if item['algorithm'] == algorithm:
              algo_setting = item
      if algo_setting is None:
          raise Exception('Settings for algorithm not found in algo_response parameter')
      return algo_setting

  def get_accounts(self):
      return self.request('GET', '/main/api/v2/accounting/accounts2/', '', None)

  def get_accounts_for_currency(self, currency):
      return self.request('GET', '/main/api/v2/accounting/account2/' + currency, '', None)

  def get_withdrawal_addresses(self, currency, size, page):
      params = "currency={}&size={}&page={}".format(currency, size, page)
      return self.request('GET', '/main/api/v2/accounting/withdrawalAddresses/', params, None)

  def get_withdrawal_types(self):
      return self.request('GET', '/main/api/v2/accounting/withdrawalAddresses/types/', '', None)

  def withdraw_request(self, address_id, amount, currency):
      withdraw_data = {
          "withdrawalAddressId": address_id,
          "amount": amount,
          "currency": currency
      }
      return self.request('POST', '/main/api/v2/accounting/withdrawal/', '', withdraw_data)

  def get_my_active_orders(self, algorithm, market, limit):
      ts = self.get_epoch_ms_from_now()
      params = "algorithm={}&market={}&ts={}&limit={}&op=LT".format(algorithm, market, ts, limit)
      return self.request('GET', '/main/api/v2/hashpower/myOrders', params, None)

  def create_pool(self, name, algorithm, pool_host, pool_port, username, password):
      pool_data = {
          "name": name,
          "algorithm": algorithm,
          "stratumHostname": pool_host,
          "stratumPort": pool_port,
          "username": username,
          "password": password
      }
      return self.request('POST', '/main/api/v2/pool/', '', pool_data)

  def delete_pool(self, pool_id):
      return self.request('DELETE', '/main/api/v2/pool/' + pool_id, '', None)

  def get_my_pools(self, page, size):
      return self.request('GET', '/main/api/v2/pools/', '', None)

  def get_hashpower_orderbook(self, algorithm):
      return self.request('GET', '/main/api/v2/hashpower/orderBook/', 'algorithm=' + algorithm, None )

  def create_hashpower_order(self, market, type, algorithm, price, limit, amount, pool_id, algo_response):
      algo_setting = self.algo_settings_from_response(algorithm, algo_response)
      order_data = {
          "market": market,
          "algorithm": algorithm,
          "amount": amount,
          "price": price,
          "limit": limit,
          "poolId": pool_id,
          "type": type,
          "marketFactor": algo_setting['marketFactor'],
          "displayMarketFactor": algo_setting['displayMarketFactor']
      }
      return self.request('POST', '/main/api/v2/hashpower/order/', '', order_data)

  def cancel_hashpower_order(self, order_id):
      return self.request('DELETE', '/main/api/v2/hashpower/order/' + order_id, '', None)

  def refill_hashpower_order(self, order_id, amount):
      refill_data = {
          "amount": amount
      }
      return self.request('POST', '/main/api/v2/hashpower/order/' + order_id + '/refill/', '', refill_data)

  def set_price_hashpower_order(self, order_id, price, algorithm, algo_response):
      algo_setting = self.algo_settings_from_response(algorithm, algo_response)
      price_data = {
          "price": price,
          "marketFactor": algo_setting['marketFactor'],
          "displayMarketFactor": algo_setting['displayMarketFactor']
      }
      return self.request('POST', '/main/api/v2/hashpower/order/' + order_id + '/updatePriceAndLimit/', '',
                          price_data)

  def set_limit_hashpower_order(self, order_id, limit, algorithm, algo_response):
      algo_setting = self.algo_settings_from_response(algorithm, algo_response)
      limit_data = {
          "limit": limit,
          "marketFactor": algo_setting['marketFactor'],
          "displayMarketFactor": algo_setting['displayMarketFactor']
      }
      return self.request('POST', '/main/api/v2/hashpower/order/' + order_id + '/updatePriceAndLimit/', '',
                          limit_data)

  def set_price_and_limit_hashpower_order(self, order_id, price, limit, algorithm, algo_response):
      algo_setting = self.algo_settings_from_response(algorithm, algo_response)
      price_data = {
          "price": price,
          "limit": limit,
          "marketFactor": algo_setting['marketFactor'],
          "displayMarketFactor": algo_setting['displayMarketFactor']
      }
      return self.request('POST', '/main/api/v2/hashpower/order/' + order_id + '/updatePriceAndLimit/', '',
                          price_data)

  def get_my_exchange_orders(self, market):
      return self.request('GET', '/exchange/api/v2/myOrders', 'market=' + market, None)

  def get_my_exchange_trades(self, market):
      return self.request('GET','/exchange/api/v2/myTrades', 'market=' + market, None)

  def create_exchange_limit_order(self, market, side, quantity, price):
      query = "market={}&side={}&type=limit&quantity={}&price={}".format(market, side, quantity, price)
      return self.request('POST', '/exchange/api/v2/order', query, None)

  def create_exchange_buy_market_order(self, market, quantity):
      query = "market={}&side=buy&type=market&secQuantity={}".format(market, quantity)
      return self.request('POST', '/exchange/api/v2/order', query, None)

  def create_exchange_sell_market_order(self, market, quantity):
      query = "market={}&side=sell&type=market&quantity={}".format(market, quantity)
      return self.request('POST', '/exchange/api/v2/order', query, None)

  def cancel_exchange_order(self, market, order_id):
      query = "market={}&orderId={}".format(market, order_id)
      return self.request('DELETE', '/exchange/api/v2/order', query, None)

if __name__ == "__main__":
 parser = optparse.OptionParser()
 parser.add_option('-b', '--base_url', dest="base", help="Api base url", default="https://api2.nicehash.com")
 parser.add_option('-o', '--organization_id', dest="org", help="Organization id")
 parser.add_option('-k', '--key', dest="key", help="Api key")
 parser.add_option('-s', '--secret', dest="secret", help="Secret for api key")
 parser.add_option('-m', '--method', dest="method", help="Method for request", default="GET")
 parser.add_option('-p', '--path', dest="path", help="Path for request", default="/")
 parser.add_option('-q', '--params', dest="params", help="Parameters for request")
 parser.add_option('-d', '--body', dest="body", help="Body for request")
 options, args = parser.parse_args()
 private_api = private_api(options.base, options.org, options.key, options.secret)
 params = ''
 if options.params is not None:
     params = options.params
 try:
     response = private_api.request(options.method, options.path, params, options.body)
 except Exception as ex:
     print("Unexpected error:", ex)
     exit(1)
 print(response)
 exit(0)

CoinGecko API (2-2で説明)

marketrate.py
import requests
import json
class trade_table:
    def __init__(self, market="BTC"):
        #currency-name conversion table
        self.currency_rename_table = {'BTC':'Bitcoin','ETH':'Ethereum','LTC':'Litecoin',
                                      'XRP':'XRP','RVN':'Ravencoin','MATIC':'Polygon',
                                      'BCH':'Bitcoin Cash','XLM':'Stellar','XMR':'Monero','DASH':'Dash'}
        self.market = self.currency_rename_table[market]

    def get_rate(self):
        body = requests.get('https://api.coingecko.com/api/v3/coins/markets?vs_currency=jpy')
        coingecko = json.loads(body.text)
        idx = 0
        while coingecko[idx]['name'] != self.market:
            idx += 1
            #Escape of illegal market_currency name
            if idx > 100:
                return "trade_table_err"
        #market-currency_to_JPY_rate
        else:
            return int(coingecko[idx]['current_price'])

S3バケットへの収益情報の読み込み・書き出し(2-3で説明)

s3inout.py
import boto3
class s3_dealer:
  def __init__(self, bucket = 'nice-hash-balance', key = 'balance_latest.txt'):
    self.bucket = bucket
    self.key = key

  #Get balance of the previous day
  def read_from_s3_bucket(self):
    S3 = boto3.client('s3')
    res = S3.get_object(Bucket=self.bucket, Key=self.key)
    body = res['Body'].read()
    return body.decode('utf-8')

  #Export balance
  def write_to_s3_bucket(self, balance):
    S3 = boto3.resource('s3')
    obj = S3.Object(self.bucket, self.key)
    obj.put(Body=balance)

2-1-4.レイヤー作成

必要なモジュールをLambdaのレイヤーに取り込む
2-1-3 記載のソースをデプロイしただけで実行するとrequestsモジュールが読み込めず以下エラーが発生してしまうため、外部モジュールをLayersへ定義する

{
  "errorMessage": "Unable to import module 'lambda_function': No module named 'requests'",
  "errorType": "Runtime.ImportModuleError"
}

・レイヤーファイルを作成するために、EC2でAmazon Linux AMIから新規インスタンスを作成する
2-1-1の手順で、EC2のロールに対してS3のアクセスポリシーをアタッチ
 ※インターネット環境に接続されたWSLやUbuntu等のUNIXマシンであれば何でもOK
・EC2インスタンスへコンソール接続し、以下CLIコマンドを打鍵してレイヤーファイルを作成する

ec2-user
[ec2-user@ip-xxx-xx-xx-xxx ~]$ su -
[root@ip-xxx-xx-xx-xxx ~]# mkdir layer/
[root@ip-xxx-xx-xx-xxx ~]# cd layer
[root@ip-xxx-xx-xx-xxx ~]# yum -y install gcc gcc-c++ kernel-devel python-devel libxslt-devel libffi-devel openssl-devel
[root@ip-xxx-xx-xx-xxx ~]# yum -y install python-pip
[root@ip-xxx-xx-xx-xxx ~]# pip install -t ./ requests
[root@ip-xxx-xx-xx-xxx ~]# cd ../
[root@ip-xxx-xx-xx-xxx ~]# zip -r Layer.zip layer/

・レイヤーファイルを、S3バケットへアップロード

ec2-user
[root@ip-xxx-xx-xx-xxx ~]# chmod 777 Layer.zip
[root@ip-xxx-xx-xx-xxx ~]# aws s3 cp Layer.zip s3://layerzip-s3

・S3でEC2からアップロードしたレイヤーファイルのオブジェクトURLを取得
image.png

・LambdaでS3のオブジェクトURLから名前を適当にImportRequestsとしてレイヤーを作成
image.png

・Lambdaで「レイヤーの追加」をクリック
image.png

・カスタムレイヤーから作成したImportRequestsを読み込む
image.png

2-1-5.タイムアウト値の延長

タイムアウトエラーを回避するためにタイムアウト値を変更する
・Lambdaはデフォルトだと、メモリ:128MB、タイムアウト:3秒になっているため、タイムアウトのみ「3秒5秒」へ変更する
image.png

2-2.APIによる収益情報取得

外部APIからマイニング収益情報を取得する
・LambdaとNiceHash APIを連携するために、NiceHashへログインしてMySettingsからAPI Keysを発行する
image.png
NiceHash API(nicehash.py)で収益情報を取得するために、lambda_function.pyの対象箇所に発行したAPI Keys、組織IDを入力する

lambda_function.py
#NiceHash API    
host = 'https://api2.nicehash.com'
organisation_id = 'xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx' # hogehoge
key = 'xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx' # API Key Code
secret = 'xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx' # API Secret Key Code

・NiceHash APIのみでは、円相場の情報は取得できないため、別の外部API CoinGecko API(marketrate.py)を呼び出して、各仮想通貨市場の日本円相場を取得する。

2-3.EventBridgeによるトリガー定義

日次ジョブとしてLambdaをキックするためのトリガーを定義する
・Lambdaから「トリガーの追加」をクリック
・EventBridgeを選択し、以下のように入力し「追加」をクリック
 ※AWSでcronを定義する際は、crontabとの違いや時差を考慮する必要があるため注意する 2

ルール:「新規ルールの作成」
ルール名:DailyTrigger
ルールタイプ:スケジュール式
スケジュール式:cron(0 15 * * ? *) # 毎日0:00に実行するcron

image.png

2-4.S3バケットへの書き出し・読み込み

前日の収益との比較を行うため、S3バケットのファイルに対して書き出し・読み込みを行う
・S3バケットbucket_nameを作成して、前日の残高(円)を整数で記載したダミーファイルbalance_filename.csvを予め格納しておく

bucket_name/balance_filename.csv
28583

・LambdaとS3のサービス間で連携するために、lambda_function.pyの対象箇所を編集する

lambda_function.py
#NiceHash API    
#S3 bucket
bucket_name = '[bucket_name]' # S3バケット名
key_name = '[balance_filename.csv]' # 残高情報が記載されたファイル名

2-5.Amazon SNSによるメール通知

取得した収益情報をメール通知するために、「SNSとメール」「LambdaとSNS」の連携を行う
・Amazon SNSを起動し、適当にNiceHashSNSとしてトピックを作成する
・作成したトピックNiceHashSNSのARNをコピーする
image.png
・「SNSとメール」を連携するために、作成したトピックNiceHashSNSを開き、「サブスクリプションの作成」をクリック
image.png
・プロトコル:Eメール、エンドポイント:通知したいメールアドレスを指定し、「サブスクリプションの作成」をクリック
・サブスクリプションを作成すると、指定したメールアドレスに通知メールが届くので、挿入されたリンクへアクセスしサブスクリプションのアクティベーションを行う
・「LambdaとSNS」を連携するために、lambda_function.pyの対象箇所を編集する

lambda_function.py
#Amazon SNS
TOPIC_ARN = 'arn:aws:sns:xx-xxxx-x:xxxxxxxxxx:NiceHashSNS' # コピーしたSNSのARNを記載

3. 実行結果

・毎日0:00になるとEventBridgeがLambdaをキックして、日次の通知メールが来るようになりました。
IMG_1134.PNG

4. 終わりに

・APIで情報取得している割に、通知はメールというのは構成的にビミョかったので、この後にLINE APIを活用してLINE通知する構成に変更しました。次回は、LINE通知する構成について投稿予定です。

5. 更新履歴

ver. 1.0 初版投稿 2021/02/28

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

Golangはじめて物語(第8話: DynamoDBのデータモデリングのベストプラクティスに従った場合の実装方法)

はじめに

最近、DynamoDB のデータモデリングを勉強しているが、これに従ってみようとすると、あれ?なんか Golang での実装が難しくないか?と思って調べてみた。
※Python だったらディクショナリ型を使えばお手軽に扱える気がするのだけど。

データモデリングのベストプラクティスについては、以下の記事を参考にすると良い。

何が難しいか?

いずれの記事でも、同じハッシュキーにできるものは1つのテーブルに寄せて、属性が異なるものはソートキーで種別を分ける、と書かれている。
例えば、あるハッシュキーに紐づく情報をqueryで一発で引くと、以下のように personalcompany といった異なる属性を持ったアイテムが取得されることになる。

{
    "Items": [
        {
            "department": {
                "S": "sales"
            },
            "grade": {
                "S": "A"
            },
            "id": {
                "S": "00001"
            },
            "type": {
                "S": "company"
            }
        },
        {
            "birthdt": {
                "S": "19800101"
            },
            "postalcode": {
                "S": "1740001"
            },
            "id": {
                "S": "00001"
            },
            "telno": {
                "S": "09000000000"
            },
            "name": {
                "S": "Taro"
            },
            "type": {
                "S": "personal"
            }
        }
    ],
    "Count": 2,
    "ScannedCount": 2,
    "ConsumedCapacity": null
}

これを Golang で実装しようとした場合、もちろん GetItem() を2回呼ぶという方法もあるが、それだとコードが冗長になってしまうケースがある。Query() で一発で取得しようとしたらどうしたら良いだろうか?

考えてみる

上記のJSONを、JSON-to-Go に食わせてみると、以下のように出力される。

type AutoGenerated struct {
    Items []struct {
        Department struct {
            S string `json:"S"`
        } `json:"department,omitempty"`
        Grade struct {
            S string `json:"S"`
        } `json:"grade,omitempty"`
        ID struct {
            S string `json:"S"`
        } `json:"id"`
        Type struct {
            S string `json:"S"`
        } `json:"type"`
        Birthdt struct {
            S string `json:"S"`
        } `json:"birthdt,omitempty"`
        Postalcode struct {
            S string `json:"S"`
        } `json:"postalcode,omitempty"`
        Telno struct {
            S string `json:"S"`
        } `json:"telno,omitempty"`
        Name struct {
            S string `json:"S"`
        } `json:"name,omitempty"`
    } `json:"Items"`
    Count            int         `json:"Count"`
    ScannedCount     int         `json:"ScannedCount"`
    ConsumedCapacity interface{} `json:"ConsumedCapacity"`
}

つまり、両方の属性を持った構造体を定義して、取得できなかったものは omitempty すれば良いようだ。
omitempty は、Unmarshal() 時に該当の属性がJSONに入っていなかった場合に、空値にしてくれる。

上記は、あくまでもJSONへのマッピングなので、DynamoDBAttributeValue から Unmarshal() するには、以下のように定義する。

type item struct {
    Id   string `dynamodbav:"id"`
    Type string `dynamodbav:"type"`
    Name string `dynamodbav:"name,omitempty"`
    BirthDt string `dynamodbav:"birthdt,omitempty"`
    TelNo string `dynamodbav:"telno,omitempty"`
    PostalCode string `dynamodbav:"postalcode,omitempty"`
    Department string `dynamodbav:"department,omitempty"`
    Grade string `dynamodbav:"grade,omitempty"`
}

ただし、Query() で複数レコード帰ってくるときは配列なので、さらに以下のように配列化するための構造体を作る。

type items struct {
    Item []item
}

この構造体を活用して、以下のように Query() の結果を Unmarshal() しよう。

var items items

result, err := ddb.Query(&dynamodb.QueryInput{
    TableName: aws.String("table-name"),
    KeyConditionExpression: aws.String("#id = :id"),
    ExpressionAttributeNames: map[string]*string{
        "#id": aws.String("id"),
    },
    ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
        ":id": {
            S: aws.String(string(id)),
        },
    },
})

err = dynamodbattribute.UnmarshalListOfMaps(result.Items, &items.Item)

これで、無事、item の配列にマッピングできた状態になる。

さて、これであとは煮るなり焼くなり好きにすれば良いが、せっかくなので、2種類のレコードをお手軽に取得できるようにしてみよう。
※単純に JSON を返せば良いのであれば、さらにここから、別の JSON 定義で marshal() すれば良いが、今回は別の構造体にマッピングしてみる。

type personal struct {
    Id         string
    Name       string
    BirthDt    string
    TelNo      string
    PostalCode string
}

type company struct {
    Id         string
    Department string
    Grade      string
}

type itemsInterface interface {
    getPersonal() personal
    getCompany() company
}

func (items items) getPersonal() personal {
    for _, item := range items.Item {
        if item.Type == "personal" {
            return personal{
                Id:         item.Id,
                Name:       item.Name,
                BirthDt:    item.BirthDt,
                TelNo:      item.TelNo,
                PostalCode: item.PostalCode,
            }
        }
    }
    return personal{}
}

func (items items) getCompany() company {
    for _, item := range items.Item {
        if item.Type == "company" {
            return company{
                Id:         item.Id,
                Department: item.Department,
                Grade:      item.Grade,
            }
        }
    }
    return company{}
}

あとは、Unmarshal() 後に以下のように呼び出せばよい。

    personal := items.getPersonal()
    company  := items.getCompany()

これでもまだ冗長な気がするが、それでも単に GetItem() するよりは、スマートに作れている……かな……?

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

AWS認定 SysOps Administrator Associateで不合格になった話

スクリーンショット 2021-02-19 20.20.25.png

はじめに

皆さん、こんにちは!!
ハンズラボのサムです!!
先日AWS認定 セキュリティ専門知識を合格したので、勢いに乗ってアソシエイトの中では簡単と言われるSysOps Administrator Associateを受けてきました!

因みに今のバッジはこんな感じで、正直SAAもってるから余裕だと思ってました。
スクリーンショット 2021-02-19 20.31.35.png

目的

AWS資格12冠ですが、ひとまずアソシエイト3冠しようというスタイルです!

スケジュール&試験対策

試験対策期間:3日間ぐらい

いつもどおり
* Udemyの模擬試験
* AWSの公式模試
で対策をやりました。

因みに模試はこんな感じでした↓(この合格ラインギリギリ感)
スクリーンショット 2021-02-18 15.25.46.png

実際のテストの雰囲気など

難易度としては専門知識とかと比べて問題の長さは短くSAAと同程度でしたが、SAAより若干掘り下げた問題が多かったように感じます。
それと、問題に登場するサービスが若干古かったので試験自体そろそろ更新なのかな?といった感じでした。

問題量に対して試験時間は長いぐらいで見直ししても50分程度余ったので、もうちょっとゆっくり解いてもよかったかな〜?という印象でした。
そして、分からない問題よりわかる問題が多かったので受かっただろうと思ってました。

結果

はい。表題の通り落ちました!!!!
誰だよ、SysOpsは簡単だって書いた人!!
点数は677点なので多分2-3問足りなかった感じ。

敗因として
* CloudFormationの問題がわからないの多かった・・・
* 完全に慢心していた
以上!!!!

はい、ちゃんと再度対策してサービス調べて勉強し直してきます!!!!
2週間後には再試験できるようになるのでリベンジしてきます。
皆様もネットに簡単と書いてあっても鵜呑みにしないように!!
満身は最大の敵です!!

以上、自戒を込めた記事でした!!次の記事は合格になった話にもってけるようにします!!
スクリーンショット 2021-02-19 20.20.14.png
スクリーンショット 2021-02-19 20.20.25.png

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

AWS 認定クラウドプラクティショナー (CLF) 取得メモ

AWS 認定クラウドプラクティショナー(CLF)取得のためのメモ。
あくまで自分が取得するためのメモ。

ポイントメモ

概念

スケールアップ:単体性能を向上
スケールアウト:台数を増加

  • AWS Well-Architectedフレームワーク
  • 運用上の優秀性、セキュリティ、信頼性、パフォーマンス効率、コスト最適化

セキュリティ系

  • AWS Shield : DDOS保護サービス
  • AWS WAF : マネージド型Webアプリケーションファイアウォール
    • セキュリティルールは自分で定義する必要がある
    • 適用サービスはCloudFront/ALB/API Gatewayから選択できる
  • Amazon Inspector : 脆弱性診断を自動で行うことができるサービス
  • Amazon GuardDuty : AWS インフラストラクチャとリソースに対してインテリジェントな脅威検出を提供するサービスです。AWS 環境内のネットワークアクティビティとアカウント動作を継続的にモニタリングし、脅威を特定します。
  • AWS Artifact は、AWS のセキュリティおよびコンプライアンスレポートのオンデマンドでの利用と、特定のオンライン契約を提供するサービスです。

AWS テクノロジー

  • リージョンにはアベイラビリティゾーンが2つ以上ある
    • 同一リージョンのAZ同士は高速なプライベートネットワークで接続されている
  • リージョンとは異なる場所に200以上のエッジロケーションがある
  • エッジロケーションではRoute 53とCloudFrontを利用できる (AWS Shieldも)

コンピューティングサービス

  • インスタンスタイプ : ファミリー/世代/サイズ t2.mircro
  • ELBのタイプ
    • Network Load Balancer : HTTP/HTTPSで利用する場合こちらを使う
    • Classic Load Balancer : HTTP/HTTPSのプロトコル以外 Auto Scaling
  • 垂直スケーリング : インスタンスタイプを変更
  • 水平スケーリング : =AutoScaling 同一インスタンスのサーバを増加
  • Auto Scaling ポリシー : 何を(起動設定)、どこで(Auto Scalingグループ)、いつ(いつのタイミングで起動/終了するか)
  • EC2の料金について
    • スポットインスタンス : スポットインスタンスとは、AWSサーバ上に存在し、使われていないEC2インスタンスに値段をつけ、その入札価格が現在のスポット価格(※長期ではなく、1回ごとの取引で決定され成立した市場価格)を上回っている限り、そのインスタンスを利用することができるというものです。中断の可能性があるので長時間処理などには向かない。
    • 同一リージョン内であれば、他アカウントでもデータ転送料金は発生しない

ストレージ

  • EBS
    • 汎用SSD : 最大16,000IOPS
    • プロビジョンドIOPS SSD : 最大64,000
    • スループット最適化HDD, Cold HDD : コスト節約
    • アベイラビリティゾーン内で自動的にレプリケートされる
    • 使い始めた後にオンラインでボリュームタイプの変更が可能
    • 1つのアベイラビリティゾーン
  • EFS
    • 完全マネージド型の NFS ファイルシステムを
    • 複数のアベイラビリティゾーン
  • S3
    • S3 Glacier ストレージクラスに保存されているオブジェクトは、数分から数時間以内に取得できます。
    • S3 Glacier Deep Archive ストレージクラスに保存されているオブジェクトは、12 時間以内に取得できます。

ネットワーク

  • VPC
    • リージョンを選択して作成。
    • CIDRでVPCのプライベートIPアドレスの範囲を定義
  • サブネット
    • サブネットはアベイラビリティゾーンを選択して作成
    • CIDRでサブネットのプライベートIPアドレスの範囲を定義
  • セキュリティグループ
    • インスタンスに対してのトラフィックを制御する仮想ファイアウォール
    • 許可するインバウンドのポートと送信元を設定するホワイトリスト
    • 送信元にはCIDRか他のセキュリティグループIDを指定できる
    • セキュリティグループはステートフルであり、デフォルトですべてのインバウンドトラフィックを拒否する
  • ネットワークACL
    • サブネットに対してのトラフィックを制御する仮想ファイアウォール
    • 拒否するインバウンドのポートと送信元を設定するブラックリスト
    • 必要がなければ設定しない
  • CloudFront
    • エッジロケーションを使用するCDN
  • Route 53
    • シンプルルーティング/レイテンシベースのルーティング/場所に基づくルーティング

データベース

  • RDS
    • データベースのバックアップを管理しなくて良い
    • バックアップ期間中の任意の特定時間のインスタンスを起動できる
  • DMS
    • データベース間でデータを移行できるサービス
    • オンプレからの継続的なデータ移行を行いシステムのダウンタイムを最小限にできる

管理サービス

  • Trusted Advisor
    • コスト最適化、パフォーマンス、セキュリティ、フォールトトレランス、サービス制限をチェック

請求と料金

  • ECO計算ツール : AWS導入検討の際のコスト比較
  • AWS Budgets : では、サービス使用量が予算の量を超えた場合 (または超えることが予想される場合) に通知するカスタムアラートを設定できます。

AWS Cloud Practitioner Essentials (Japanese) で学習したメモ

  • インスタンスストア : Amazon EC2 インスタンスを停止または削除すると、アタッチされたインスタンスストアに書き込まれたデータはすべて削除されます。
  • Amazon Quantum Ledger Database (Amazon QLDB)…は台帳データベースサービスです。Amazon QLDB では、アプリケーションのデータに対して行われたすべての変更を包括的に確認できます。
  • Amazon Textract は、電子化したドキュメントからテキストとデータを自動抽出する機械学習サービスです。
  • Amazon Lex は、音声とテキストを使用して会話型インターフェイスを構築できるサービスです。
  • Amazon Augmented AI (Amazon A2I)…コンテンツモデレーションやドキュメントからのテキスト抽出など一般的な機械学習のユースケース向けの、人によるレビューワークフローをビルトインで提供しています。
  • AWS Cloud Adoption Framework
    • プラットフォームパースペクティブ : 新しいソリューションを導入したり、オンプレミスのワークロードをクラウドに移行したりするための原則も含まれています。
    • オペレーションパースペクティブ : ビジネスの関係者の要件を満たすことができるように IT ワークロードの運用と復旧に焦点を合わせます。
  • 移行戦略
    • リホスト
    • リプラットフォーム
    • リファクタリング/アーキテクチャの再設計
    • 再購入 (Repurchase)
    • 保持 (Retain) …ソース環境でビジネスに重要なアプリケーションを維持することです。
    • リタイア
  • Well-Architected フレームワーク
    • 運用上の優秀性 : 運用上の優秀性は、システムを運用し監視してビジネス価値をもたらし、付随するプロセスと手順を継続的に向上させることができる能力です。
    • セキュリティ : リスクの評価と軽減の戦略によってビジネス価値を生み出しながら、情報、システム、アセットを保護する能力です。
    • パフォーマンス効率 : コンピューティングリソースを効率よく使用し、システム要件を満たし、需要の変更や技術の進化に応じて効率性を維持する能力を示します。
    • 信頼性
    • コスト最適化

AWS 認定クラウドプラクティショナー 模擬試験問題集復習用

  • AWS Config はAWS リソースの設定を評価、監査、審査できるサービス。
  • EC2 Dedicated hostはオンデマンドインスタンスを物理的に占有する場合に選択するオプション設定です。
  • EFS : EC2インスタンスからLAN上にあるNASとして利用できる共有ファイルストレージとして提供されています。複数のEC2インスタンスから接続・共有可能なストレージとして機能する。
  • Amazon Auroraの DB インスタンスでは常に自動バックアップが有効。
  • Amazon S3 Transfer Acceleration : エッジロケーションを利用してクライアントと S3 バケットの間で、長距離にわたるファイル転送を高速、簡単、安全に行えるようになります。
  • Amazon EMR は、Apache Hadoop や Apache Spark などのビッグデータフレームワークとして、大量のデータを処理および分析するマネージド型クラスタープラットフォームです。
  • AWS CloudTrail は、AWS アカウントのガバナンス、コンプライアンス、運用監査、リスク監査を行うためのサービスです。
  • 初めからマルチAZ構成のフォールトトレランスを考慮して設計されているAWSサービス → Dynamo DB/S3
  • EC2のAWS無料利用枠 → 12ヶ月
  • AWS Storage Gateway → 実質的に無制限なクラウドストレージへの、オンプレミスでのアクセス
  • AWS OpsWorks Chef や Puppet のマネージド型インスタンスを利用できるようになる構成管理サービスです。
  • AWS 料金計算ツールは、AWSユーザーが月間AWS請求書をより効率的に見積もるのに役立ちます。
  • AWS Global Accelerator : ローカルまたは世界中のユーザーに提供するアプリケーションの可用性とパフォーマンスを改善します。
  • AWS CodeCommit : フルマネージド型のソース管理サービスであり、企業が安全で拡張性の高いプライベートGitリポジトリを簡単にホストできるようにします。
  • AWS Security Token Service (AWS STS) : AWS のサービスへのアクセスに使用できる一時的な限定権限認証情報を取得できます。
  • Amazon Elastic Container Registry (ECR) : 開発者がDockerコンテナイメージをAWSクラウドに保存・管理することができます。
  • AWSで提供されているデータベースサービスのうち、RDS、Redshift、ElastiCache Redisには自動バックアップ(自動スナップショット)の機能がある。

学習方法

  • AWS ホワイトペーパー
  • AWS トレーニング
  • AWS Black Belt Online Seminar
  • AWS クラウドサービス活用資料集トップ
  • 10分間チュートリアル
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【AWS】S3で静的WEBサイトホスティングを使ってWEBページを公開する

はじめに

S3とはSimple Storage Serviceの略で、その名の通りファイルをアップロードして保存することができるストレージサービスです。
しかしS3は単なるストレージサービスではなく、HTMLファイルをアップロードして簡単なWEBページを作成することができます。

簡単な用語の説明

・バケット……オブジェクトを格納するためのコンテナのこと。いわゆる頂点となるフォルダ。
・オブジェクト……S3バケットに格納されるデータのこと。ファイルなど。

用意するもの

次のHTMLファイル

index.html
<!DOCTYPE html>
<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  This is Test Page!
 </body>
</html>

手順

S3のコンソールを開きます。バケットを作成をクリックします。
S3_1.png
バケット名はグローバルに一意である必要があります。つまり他のどんなS3バケットと名前が被ってはいけません。
リージョンはアジアパシフィック(東京)を選択します。
今回は外部に向けてWEBページを公開したいので「パブリックアクセスのブロック」はオフにします。
他の設定はデフォルトのままでバケットを作成します。
S3_2.png
バケットが作成されたのでバケットを開きます。
オブジェクトをアップロードします。
S3_3.png
ファイルを追加で用意したindex.htmlをアップロードします。
S3_4.png
アップロードが成功しました。
S3_5.png
バケットのプロパティを開きます。
S3_6.png
下へスクロールして静的ウェブサイトホスティングを編集します。
S3_7.png
静的ウェブサイトホスティングを有効にします。
インデックスドキュメントは「index.html」とします。
変更の保存をクリックします
S3_8.png
これだけではまだブラウザから目的のページにアクセスできません。
試しにオブジェクトのURLからアクセスしてみます。
S3_9.png
このようなエラーが返されます。これはS3バケットの権限が足りていないため生じるエラーです。
S3_10.png
バケットのアクセス許可を開きます。
S3_11.png
下へスクロールし、バケットポリシーを編集します。
S3_12.png
画像のようにポリシーを記述します。
S3_13.png
このJSONテキストは公式ドキュメントから参照可能です。
xxxxxxxxの部分がバケット名です。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicReadGetObject",
            "Effect": "Allow",
            "Principal": "*",
            "Action": [
                "s3:GetObject"
            ],
            "Resource": [
                "arn:aws:s3:::xxxxxxxx/*"
            ]
        }
    ]
}

各項目を簡単に見て行きましょう。
Versionは「ポリシーを処理するために使用される言語構文ルール」を指定します。2012-10-17が現行バージョンです。
Statementはポリシーの主要な要素です。
SidはステートメントIDの略で、ポリシーの任意の識別子です。
Effectはステートメントの結果を許可するか拒否するかを指定します。許可する場合はAllow、拒否する場合はDenyです。
Principalは「リソースへのアクセスを許可または拒否するユーザー、アカウント、サービス」などを指定します。*はワイルドカードで、誰でもアクセスできることを意味します。
Actionは対象(リソース)に対して許可または拒否したいアクションを指定します。GetObjectはS3からオブジェクトを取得できるアクションです。その他アクションはこちら(英語版公式ドキュメント)
ResourceはバケットやオブジェクトなどのARN(Amazon Resource Name)を指定します。バケットのARNの後ろに/*と追記してあげることでバケットに格納されているオブジェクトすべてを指定しているわけです。

さて、WEBページを公開する準備は整いました。
オブジェクトURLからページを開いてみましょう。
S3_14.png
無事に表示されました。

参考


この記事はAWS初学者を導く体系的な動画学習サービス
「AWS CloudTech」の課題カリキュラムで作成しました。
https://aws-cloud-tech.com

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

試しにAWSのEC2にwordpressを設置してみた

前提

データベースが必要であり、そのデータベースを設置するために2つのAZが必要になります。ここではap-northeast-1a,1cを使用します。データベースはap-northeast-1aに設置します。以下の構造を作ります。

注意

あくまで試しに作る環境なので、環境を作って動作を確認したら課金が発生する前に環境を削除したほうがいいです。

ap-northeast-1a(AZ)
PublicSubnet1 10.0.0.0/24
PrivateSubnet1 10.0.2.0/24 ここにデータベースを設置します

ap-northeast-1c(AZ)
PublicSubnet2 10.0.1.0/24
PrivateSubnet2 10.0.3.0/24 ここにデータベースを設置します

手順

1)以下のようにVPCを作成する。
Name: MyVPC
IPv4 CIDR: 10.0.0.0/21 このネットワークを手順2)で分割します

2)作成したVPC内に下記のサブネットを作る。
PublicSubnet1 10.0.0.0/24
PublicSubnet2 10.0.1.0/24
PrivateSubnet1 10.0.2.0/24
PrivateSubnet2 10.0.3.0/24

3)EC2を作成する。
OS: AmazonLinux2
インスタンスタイプ: t2micro
ネットワーク: MyVPC1
サブネット: PublicSubnet1
自動割り当てパブリックIP: 有効
ストレージボリューム: とりあえず8GB(デフォルト)
タグ付け(任意)
セキュリティグループ設定でhttp通信を許可する。必要であれば、httpsも許可する。因みにサーバーにping応答させたければ、ICMPを許可してください。
キーペア: 既存のキーペアがあれば、それを使い、まだキーペアがなければ、キーペアを作成してください。

4)インターネットGWを作成し、VPCへアタッチする。

5)PublicSubnet1のルートテーブルに以下を追加してください。
ルート: 0.0.0.0/0
ターゲット: 手順4)で作成したGW

6)データベースのサブネットグループを以下のように作成してください。ここで以降の手順で作成するデータベースをどのAZのどのVPCのどのサブネットに割り当てるかを決定します。
名前: MysubnetGroup
説明: MysubnetGroup
VPC: MyVPC1
AZ: ap-northeast-1a,1c(前提として2つのAZが必要です。)
サブネット: PrivateSubnet1,2

7)以下のようにデータベースを作成してください。
データベース作成方法を選択: 標準作成
エンジンのオプション: MySQL
テンプレート: 開発/テスト
マスターユーザー名: wordpress
パスワード: 任意のパスワード
DBインスタンスサイズ: バースト可能クラス
可用性と耐久性: 「スタンバイインスタンスを作成しないでください」を選択する
接続: MyVPC1
追加の接続設定
サブネットグループ: MysubnetGroup
パブリックアクセス可能: なし(「あり」にするのは非常に危険です)
VPCセキュリティグループ: 新規作成
新しいVPCセキュリティグループ名: RDS-SG-1
AZ: ap-northeast-1a
追加設定
最初のデータベース名: wordpress

他の設定項目はデフォルトで大丈夫です。

8)RDSセキュリティグループのインバウンドルールで接続元をwebサーバーからのみに限定してください。
もともと設定されている接続元のIPアドレスを消去する
「ソース」の検索窓でsgと入力する
Web-SG-1という名前のセキュリティグループを選択する
「保存」を押す

9)作成したEC2環境にターミナルでログインし、apacheとphpをインストールしてください。

管理者権限にスイッチする
sudo su -

パッケージを最新状態にアップデートする
yum -y update

phpをインストールする
amazon-linux-extras install php7.2 -y

apacheとphpに必要なものをインストールする
yum -y install mysql httpd php-mbstring php-xml gd php-gd

apacheがサーバー起動後に自動で起動するように設定する
systemctl enable httpd.service

apacheを起動する
systemctl start httpd.service

apacheのapacheが起動しているかを確認する
systemctl status httpd.service

wordpressの最新パッケージをカレントディレクトリにダウンロードする
wget http://ja.wordpress.org/latest-ja.tar.gz ~/

カレントディレクトリにlatest-ja.tar.gzがあるかを確認する
ll

latest-ja.tar.gzを展開する
tar zxvf ~/latest-ja.tar.gz

wordpressというディレクトリが存在していることを確認する
ll

wordpressを/var/www/htmlにコピーする
cp -r ~/wordpress/* /var/www/html/

/var/www/html/配下のフォルダの所有者をapacheに変更する
chown apache:apache -R /var/www/html

以上でwordpressの閲覧が可能になります。

最後に

この記事はAWS初学者を導く体系的な動画学習サービス
「AWS CloudTech」の課題カリキュラムで作成しました。
https://aws-cloud-tech.com

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

AWS Lambda + API Gateway POSTのパラメータ取得でハマった

フロントエンドからPOST通信でアクセスするAPIをLambda+APIGatewayで作っていた時にハマった話を備忘録

申し込みフォームで登録後に完了メール送信を行うロジックを作っていた。

     const onSubmit = async () => {
       const params = {
        to: email,
        name: name,
        email: email,
        tel: tel,
        zipcode: zipcode,
        address: prefecture + " " + address,
        message: message,
      };

      const res = await axios.post(
        "https://sample/send",
        params,
        {
          headers: { "Content-Type": "multipart/form-data; charset=UTF-8" },
        }
      );
    }

Lamda側でeventの中身をのぞいてみると。。。
リクエストbodyにJSON形式で渡されると思いきや、エンコードされた状態でパラメータが渡っていたため
Lambda側でどのようにパラメータ取得を行えばよいかわからず調べること1時間...

{
 'body': 'eyJ0byI6InNhbXBsZUBnbWFpLmNvbSIsIm5hbWUiOiLlkI3liY0iLCJlbWFpbCI6InNhbXBsZUBnbWFpLmNvbSIsInRlbCI6Ijk5OTk5OTk5OTk5IiwiemlwY29kZSI6IjIxMDAwMDgiLCJhZGRyZXNzIjoi5p2x5Lqs6YO9IOS9j+aJgDHkuIHnm64iLCJtZXNzYWdlIjoi55Sz44GX6YCB44KK5LqL6aCFIn0=', 
 'isBase64Encoded': True
}

base64モジュールによりデコード処理を行うことで解決できることがわかった。

def send_mail(event, context):


    body = event["body"]
    #bodyのエンコードされた文字列をJSON文字列にデコードし、辞書型に変換
    params = json.loads(base64.b64decode(body).decode('utf-8'))

    TO        = params["to"]
    name      = params["name"]
    email     = params["email"]
    tel       = params["tel"]
    zipcode   = params["zipcode"]
    address   = params["address"]
    message   = params["message"]

    ##以下割愛

HTTPの仕組みは奥が深い....
POSTするさいのHTTPヘッダーのContent-Typeも重要であることが分かった。
何がどう異なるのかは調べなければ...

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

AWS BatchでJavaのコンテナをLambda経由で起動する時にシステム環境変数を渡す

この記事の対象

Spring Boot
EC2コンテナ
AWS Batch
AWS Lambda (node.js 14)

概要

Spring Bootで作成したバッチアプリケーションをAWS Batchに登録。
ジョブをLambda関数経由で呼び出す際にコンテナ起動パラメータを渡す方法です。

なんでそんなことを?

Batch用に作成したJavaコードに、共通処理が多いため、複数のジョブ起動が実装されており、システムプロパティ(@ConditionalOnPropertyアノテーション)を利用して、実行時に起動するジョブを選択できるようにしました。

その際に、Lambda関数側でJavaのシステムプロパティを渡す方法です。

詳細

サンプルはAWS Lambda(node.js 14)です。

sample.js
const AWS = require('aws-sdk');

module.exports = async function (param) {
    var batch = new AWS.Batch({
        httpOptions: {
            timeout: 1200000
        }
    });

    try {
        const result = await batch.submitJob({
            jobName: AWSバッチのジョブ定義名,
            jobDefinition: AWSバッチのARN,
            jobQueue: ジョブキューのARN,
            containerOverrides: {
                environment: [
                    // オーバーライドするJavaシステムプロパティを設定
                    {name: 'JAVA_OPTS', value: '-Dbatch.execute=ExecuteJob001 -Dfile.encoding=UTF-8'}
                ]
            }
        }).promise()
        console.log(result)
    } catch (err) {
        console.log("submitJob error: " + err)
        return { statusCode: 500 }
    }
};

肝は、containerOverridesパラメータです。
コンテナ起動時の環境変数をオーバーライドすることが出来ます。
このパラメータにJAVA_OPTS環境変数を定義する事で、起動するバッチアプリを選択できるようになります。

上記の例は、Javaコードで

@ConditionalOnProperty(value = {"batch.execute"}, havingValue = "ExecuteJob001")

と定義したアプリを起動する場合の引数指定方法になります。

最後に

他にも色々やり方はあるんですが、コンテナイメージを生成する際に

mvn spring-boot:build-image

を利用して安易にコンテナイメージを生成しているため、こういった方法を取りました。

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

AWSではじめてhttps通信&CDNをしてみた。超ざっくりまとめてみた。

AWSでの証明書発行およびクライアントからの通信の暗号化(https)とCludFrontを利用したCDNの実現をしました。
実現したものは、こんなイメージです。
image.png

細かい作業過程は割愛しますが環境構築で理解した基本構成の概要を自分の理解を深めるために、超ざっくりと基本要素をまとめます。

これを見れば、何の目的のために何を作るのか、一目で思い出せるはず!
また、この記事が、これからAWSの基礎学習を始める方の理解の助けとなり、AWSを触るハードルを下げる助けとなれば幸いです。私も初心者です。みなさん頑張りましょう!
qiita-square


超ざっくりまとめ

超ざっくりと説明すと、以下を行うだけです。
1. ELBでリスナー(https)登録(&SSL証明書の設定)
2. バージニアリージョンで、AWS Certificate Manager で証明書を作成
3. CloudFrontでDistributionの作成(※)
4. Route53でホストゾーンにAレコード登録(CloudFront, ELBのへのルーティング)
※Origin Domain Name(キャッシュ元情報)・証明書の登録、キャッシュに関する詳細設定を行う

image.png

これだけで、さくっと作れてしまいます。

qiita-square

CloudFrontの詳細設定方法は、この記事を参照ください。

おまけ

Amazon CloudFrontのキャッシュ機構を利用することで、グローバルなアクセスが必要なケースで、クライアントからの距離を近くし、レスポンスの向上を見込めます。
今回の記事では触れませんでしたが、AWS Global Accelerator を利⽤することによる⾮キャッシュコンテンツの⾼速化も図れ、ユースケースがいろいろと考えられる機能だと思います。(日本とネットワーク回線が細い海外拠点とのファイル共有など)
image.png

※上記は、『AWS のネットワークで知っておくべき10のこと』に分かりやすく説明されています

こちらは、別途勉強しておこうかと思います。

さいごに

この記事はAWS初学者を導く体系的な動画学習サービス「AWS CloudTech」の課題カリキュラムで作成しました。

このサービスは、テンポの良い/わかりやすい動画説明をもとに、気軽に実践を積み、自分の血肉とできるオンラインスクールです。
コミュニティも存在し、Slackで会員通しの情報交換/質問も気楽にできます。
qiita-square

書籍を購入するような値段で学習ができ、とてもお得です。(個人的な感想です)
image.png

では、また次回お会いしましょう!

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

AWS EC2 AmazonLinux2 とのファイルダウンロード・アップロード

よく忘れるのでメモ

前提
SSHでEC2に接続出来ている。

EC2のホームディレクトリからファイルダウンロード

scp -i 秘密鍵の場所.pem ec2-user@xxx.xxx.xxx.xxx:/home/ec2-user/test.txt  ./

EC2のホームディレクトリへファイルアップロード

scp -i 秘密鍵の場所.pem test.txt ec2-user@xxx.xxx.xxx.xxx:/home/ec2-user/

ディレクトリ毎操作するには -r を付ける

scp -r -i 秘密鍵の場所.pem ~以下省略~ 
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS SSMでプライベートサブネットのAmazon EC2(Windows OS)へリモートアクセスする

AWSの本番環境でプライベートサブネットにEC2インスタンスにRDPしたいという場面は結構あると思います。社内環境で利用するサーバなので、インターネットからアクセスできる状態はよろしくないので、プライベートサブネットで構築しているというパターンです。メンテナンスなどでサーバにログインしたいときによくあるパターンは踏み台サーバを別パブリックサブネットに構築して、踏み台経由でのアクセスというケースですが、そもそも踏み台サーバ分の料金が...とか、踏み台サーバの管理って誰がするの...とかの悩みが尽きないと思います。そんなときに便利なAWS System Manager(SSM)を使ったリモートデスクトップ接続をお試ししたいと思います。

今回のゴール

SSM_概要.png

こんな感じで、プライベートサブネットにデプロイしたEC2にSSM経由でリモートデスクトップ接続できることが目標です。
踏み台サーバなしで、直接PCからプライベートサブネットのEC2にアクセスできますので、シンプルかつ踏み台サーバない分料金がお安く済みます。

前提条件

  • EC2はWindows OSで構築する
  • セキュリティグループはユーザPCからRDPできるように必要なポートを解放しておく
  • ユーザPCにはAWS CLI をインストールしておく
  • 利用するリージョンは東京

構築手順

①プライベートサブネットへEC2のデプロイ
②EC2をデプロイしたVPCにSSMと接続できるエンドポイントを構築
③EC2がSSM と接続できるようにロールを作成、適用し、SSM コンソール画面のマネージドインスタンスに該当EC2が表示されるようにする
④AWS CLIを利用するIAM ユーザを作成し、IDとシークレットキーを発行して、SSM用のロールをアタッチする
⑤ユーザPCからAWS CLI でコマンド実行し、SSM経由でセッションを張る
⑥Windows のリモートデスクトップ接続でEC2へ接続

やってみる

①についてはいつものように割愛します。とりあえずWindows OSのEC2をデプロイできればOKです。
②ですがVPCのコンソールで[エンドポイント]から作成できます。
image.png

以下のように[エンドポイントの作成]を選択して、[サービスカテゴリ]から[AWSサービス]を選択し"com.amazonaws.ap-northeast-1.ec2messages"、"com.amazonaws.ap-northeast-1.ec2messages"、"com.amazonaws.ap-northeast-1.ssm"の3つのサービスを選択して、今回利用するVPCにエンドポイントを作成します。
エンドポイント作成.png
作成後、しばらくしてステータスが「利用可能」になればOKです。
image.png

続いて③ですがEC2用にまずロールを作成します。必要なポリシーは"AmazonSSMManagedInstanceCore"です。
EC2_role.png
とりあえずこのポリシーがアタッチされたロールを作成し、以下のようにEC2へアタッチします。
IAM_role_attch.png

ポリシーアタッチ後、数分後にSSM コンソール画面の[マネージドインスタンス]にアクセスし、該当EC2の[Pingの状態]が以下のようにオンラインになっていることを確認します。
オンラインになっていない場合は下記原因が考えられます。

マネージドinstance.png

【想定される原因】
- VPCのDNS設定(DHCPオプション)がおかしい
- ②で作成したSSM向けのエンドポイントがおかしい
- EC2用のIAMロールが正しくアタッチされていないか、ロールのポリシーが正しくない
- デプロイしたEC2 でSSMエージェントが起動していない

④ですがIAMユーザを作成し、CSVファイルなどでusercredential(ID/secret key)をDLして保存しておいてください。作成したIAMユーザに対して、SSMへ接続できるようにロール作成し、アタッチしていきます。
とりあえず今回ポリシーは"SSMFullAccess"にしておきます。
※FullAccessまではなくてもいいのですけどね...
SSM_IAM_role.png
今回はSSMでリモートデスクトップ接続のセッションを張るだけでなく、AWS CLI でEC2の起動もやっていたので、"AmazonEC2FullAccess"もアタッチされていますが、起動しているならこちらはなくてもOKです。

⑤ですがAWS CLI がインストールされたPCからコマンド入力して、セッションを張っていきます。
AWS CLI のインストールはAWS 公式インストールガイドからインストールしておいてください。
AWS CLI をインストールすると[C:\Users\”ユーザ".aws] にフォルダが作成され、その中の「credentials」というファイルにAWS CLIを動作させるIAM ユーザの情報が書き込まれています。

# credentials の中身
[default]
aws_access_key_id = XXXX
aws_secret_access_key = XXXXX

このcredentials のidとkey のところを④でDLしておいてたIAMユーザのものに書き換えておきます。

ちなみに同じフォルダにconfig というファイルも作成され、こちらには動作させる環境が記述されています。

# configの中身
[default]
region = ap-northeast-1
output = json

準備ができたらAWS CLIが正しくインストールされているかPCでコマンドプロンプトを立ち上げて以下コマンドで確認します。

#AWS CLIが正しくインストールされているか確認
aws --version

出力に以下のように表示されていれば、正しくインストールされています。

aws-cli/2.1.26 Python/3.7.9 Windows/10 exe/AMD64 prompt/off

続いてAWS CLI の設定が正しくされているを確認します。以下コマンドを入力します。

aws configure

以下のように表示されていれば設定OKです。

AWS Access Key ID [****************GR4Y]:   -->④で作成したIAMユーザのID
AWS Secret Access Key [****************abKl]:  -->④で作成したIAMユーザのsecret key
Default region name [ap-northeast-1]:
Default output format [json]:    

続いてEC2が起動していなければAWS CLIからEC2を起動します。

aws ec2 start-instances --instance-ids "インスタンスID" 

以下のように表示されてば正常に起動しています。

{
"StartingInstances": [
    {
        "CurrentState": {
            "Code": 0,
            "Name": "pending"
        },
        "InstanceId": "インスタンスID",
        "PreviousState": {
            "Code": 80,
            "Name": "stopped"
        }
    }
]
}

では起動したEC2に対して、SSMでセッションを張っていきます。

aws ssm start-session --target インスタンスID --document-name AWS-StartPortForwardingSession --parameters "portNumber=3389, localPortNumber=13389"

以下のように表示されればセッションは正常に構築されています。

Starting session with SessionId: SSM_access-xxxxxxxxxxxxx
Port 13389 opened for sessionId SSM_access-xxxxxxxxxx
Waiting for connections...

最後にリモートデスクトップ接続できるか確認になります。
image.png

アクセス先は[localhost:13389]にして、ユーザ名とPWを入力します。
これでリモートデスクトップ接続できればOKです。

リモートデスクトップ接続を終了するときにはコマンドプロンプトで[Ctrl]+[c]でコマンドを終了させましょう。リモートデスクトップ接続中に[Ctrl]+[c]してしまうとセッションが切れるため、リモートデスクトップ接続も切れてしまいます。

おまけ

マネージドインスタンスに表示されること= SSMでアクセスできる ということになります。
"マネージドインスタンスに表示される"状態にするために、ロールを作ったり、ポリシーをアタッチしたりしましたが実はもっと簡単に設定する方法があります、それが[高速セットアップ]です。

高速セットアップについて

image.png
SSMコンソール画面の一番上に表示されている[高速セットアップ]を選択し、どのリージョンかを選択すれば、そのリージョン内のSSMエージェントインストール済みEC2に自動的にロールをアタッチして、マネージドインスタンスに表示されるようにしてくれるみたいです。
※エンドポイントは要自力作成

SSMを使ったEC2の操作

今回構築しているリモートデスクトップ接続だけなく他にもSSMを使えば色々ことができます。
何個か参考までにご紹介します。

Windows Update

サーバにログインして、コントロールパネルからWindows Update クリックしなくてもできます。
[Run Command]を選択し、コマンドドキュメントからAWS-InstallWindowsUpdate を選択して、該当インスタンスをターゲットにしてやるだけです。
image.png

結果もこんな感じで表示されます。
image.png

サーバのイベントログの収集やレジストリの管理

image.png

ちょっとした確認ならリモートデスクトップ接続しなくても出来ちゃいます!

参考にしたサイト

https://business.ntt-east.co.jp/content/cloudsolution/column-try-27.html
https://aws.amazon.com/jp/premiumsupport/knowledge-center/ec2-systems-manager-vpc-endpoints/

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

AutoScaling/ELB/CloudWatchの組み合わせについて整理してみた

はじめに

AWSにて冗長性かつスケーラビリティのあるシステム構成を構築するにあたり、「AutoScaling」、「ELB (Elastic Load Balancing)」、「CloudWatch」の組み合わせが可能ですが、個人的に各々の設定の関連性で時に混乱してしまうため、整理をしてみました。

前提

整理をするにあたり、今回構築したAWSの構成図は以下の通りです。
構成図1.png

「ELB (Elastic Load Balancing」、「AutoScaling」、「CloudWatch」を同時に考えると混乱してしまいますので、以下の2つの組み合わせごとに分解して整理をしてみます。

番号 組み合わせ
AutoScalingとALB
AutoScalingとCloudWatch

※1. 今回、ELBの種類としてはALB (Application Load Balancer) を選択しました。

※2. Private SubnetにマルチAZでのRDSを配置し、EC2インスタンスにはWordPressをインストールすることで、ブログサービスとして構築しましたが、今回のテーマにフォーカスするため、以降の図ではPrivate Subnetは省略します。

1. AutoScalingとALB

AutoScalingとALBについて主要な設定項目を構成図に記載しました。
各々の設定で関連性のある項目を赤字としています。
構成図3.png

■ ALB側の設定 ~冗長化/負荷分散~
ALBでターゲットグループを設定することで、冗長性を実現し、ユーザからくるHTTPリクエストをターゲットグループ内で負荷分散します。仮にEC2#1がシャットダウンされた場合も、EC2#1のヘルスチェックステータスは「unused」になり、以降のHTTPリクエストはEC2#2のみで処理します。
以上のように、ALBのみでは冗長化/負荷分散が可能ですが、スケーラビリティは備えていません。
そこでスケーラビリティを加えるためにAutoScalingの設定を組み合わせます。

■ AutoScaling側の設定 ~スケーラビリティ~
上図のAutoScaling Groupで赤字の設定をすることにより、ALBのターゲットグループにスケーラビリティを加えることができます。その際、AutoScaling GroupのヘルスチェックとしてALBの設定を指定します。以上より、仮にEC2#1がシャットダウンされて、HTTPリクエストを処理できるインスタンスがEC2#2の1台に減ったとしても、希望する容量として「2」に設定しているため、新たなインスタンスを1台、自動生成することが可能になります。

2.AutoScalingとCloudWatch

AutoScalingとCloudWatchについて主要な設定項目を構成図に記載しました。
各々の設定で関連性のある項目を赤字としています。
構成図4.png

上図の構成のように、AutoScalingとCloudWatchアラームを組み合わせることにより、より柔軟にスケーリング設定をすることができます。

■ CloudWatch側の設定
CloudWatch側ではAutoScalingGroupの名前とスケーリングのアクションを指定して、アラームを作成します。
今回の場合は、AutoScalingGroup内の平均CPU使用率が70%以上の時にアラームする「CPU_High」と30%以下の時にアラームする「CPU_Low」を作成しました。各々について、アラーム発動時にAutoScalingのアクション「CPU_Add」、「CPU_Remove」が実行されます。

■ AutoScaling側の設定
AutoScaling側ではCloudWatchアラームを指定して、スケーリングポリシーを作成します。
今回の場合は、「CPU_High」のアラームが発動された際にインスタンスを1台追加する「CPU_Add」と、「CPU_Low」のアラームが発動された際にインスタンスを1台削除する「CPU_Remove」を作成しました。

以上により、CloudWatchとAutoScalingを組み合わせることにより、AutoScaling内の平均CPU使用率に応じて、インスタンス数を最小2台、最大4台にスケールする構成とすることができました。

まとめ

今回の記事作成を通し、「AutoScaling」、「ELB (Elastic Load Balancing」、「CloudWatch」を組み合わせた構成における、各々の役割と設定項目の関連性を整理することができました。

以上、最後まで読んで頂きありがとうございました!

※この記事はAWS初学者を導く体系的な動画学習サービス「AWS CloudTech」の課題カリキュラムで作成しました。

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

EC2からMySQLでRDSに接続するが、「Access denied for user」を突き返される

前提

・単一のVPC内で、EC2インスタンス用のサブネットとRDS用のサブネットを設定している。

症状

$ mysql -h [RDSのエンドポイント] -P 3306 -u root -p
Enter password: 
ERROR 1045 (28000): Access denied for user 'root'@'xx.x.x.xx' (using password: YES)

Ruby on RailsのアプリケーションをAWSにデプロイしようとしており、手順を進めていたところ、EC2インスタンスからRDSにMySQLで接続しようとして、このエラーが。
複数のサイトや記事を見てこのコマンドに問題はなさそうだったので、以下の仮説を立てました。

  1. セキュリティグループが間違っている
  2. パスワードが間違っている

結論、どちらも不正解でした。

私の場合は不正解でしたが、セキュリティグループが間違っているパターンもあるでしょうし、パスワードが間違っているパターンもあると思います。

では、何が間違っていたのか。

解決

正解はユーザー名でした。
はい、RDSのパスワードを設定するときに同時に設定するマスターユーザーの事ですね。
ご丁寧に、DB インスタンスのマスターユーザーのログイン ID を入力します。って書いてあるんですけどね。

rds.png

どの記事を見ても下記のように

$ mysql -h [RDSのエンドポイント] -P 3306 -u root -p

rootと書いてあるので、ここは疑いようもなくrootとしていたわけです。

つまり、これが正解。

$ mysql -h [RDSのエンドポイント] -P 3306 -u [マスターユーザー名] -p

まあ、マスターユーザー名を記入する時によく読まなかったのが原因なんですが。
初心者なので、コマンドの意味(-h -P -u -p)を一つひとつ調べて意味を理解していったにも関わらず、なぜかユーザー名だけは疑いもなく打ち込んでいました。

なぜなら、rootというユーザーを作成した覚えはなく、RDSを作成したときに自動的に作成されるデフォルトユーザーなのかな的な風に捉えていたからです。

補足

ちなみに、作成した後にこのユーザー名を確認するには、AWSにアクセスし、サービスからRDSを選択します。
確認したいインスタンスを選択し、設定タブを選択。その中に「マスターユーザー名」という欄があります。

解決してみて

今回は、かなり時間を費やしてしまいました。ユーザー名が間違っているというところに行き着くまでに丸1日掛かってしまいました(正確には1.5日(;゚∀゚))。挫折するかと思いました。

でも、それだけ悩みに悩んだエラーが解決したときほど嬉しいことはなく、こういうのがあるからどんどん先に進めるんですよね。

いやぁ、プログラミングは面白いなあ。

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

VPCエンドポイントとプライベートリンク

VPCエンドポイントとプライベートリンクの違い

概念

AWSアーキテクチャアイコンの分類では、PrivateLinkはサービス、VPCエンドポイント(Gateway型とInterface型)はリソースとなっています。公式ドキュメントの記載を読むとPrivateLinkはVPCエンドポイントを包括するイメージの様です。
image.png
image.png

用途

PrivateLinkを利用する用途は3種類あると動画に説明があります。
image.png

Gateway型とInterface型の違い

Gateway型はグローバルIPアドレスをInterface型はプライベートIPアドレスを使用しており、以下はGateway型がグローバルIPアドレスを使用していることを示しています。CMANで検索すると、当該IPアドレスが確かに「Amazon Technologies Inc. 」に割り当てられていることが分かります。そしてInterface型はENIを使用していることからプリベートIPアドレスを使っていることは明らかです。
image.png
オンプレミスと別リージョンからもアクセスできるInterface型の方が、机上で調べた限り、便利でセキュアな接続方法に思います。

参考情報

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

【自分用メモ】AWS認定デベロッパー-アソシエイト試験対策メモ

はじめに

本記事は、AWS-DVA試験の対策をしている上で覚えておくべきだと感じたことを
メモとして残しておくものです。
主に問題演習中に連続して間違えたものを記載しています。

AWS CLI

image.png

・--dry-run  実行可能でも実行しない。権限設定を確認する際等に使用する。
・(EC2内で) http://169.254.169.254/ にアクセスするとmeta-dataを取得可能。
・MFA認証をCLIで実施可能
・認証情報を参照する順番
1. CLIオプション --region, --output, --profile
2. 環境変数 AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN
3. credentialsファイル ~/.aws/credentials (linux mac) C:/Users/user/.aws/credentials (win)
4. configファイル /.aws/cofig (linux mac) C:/Users/user/.aws/config (win)
5. コンテナのcredentials
6. EC2のInstance profile credential

AWS SDK

image.png

・認証情報を参照する順番   
1. 環境変数
2. java system properties
3. デフォルトのcredential profileファイル
4. コンテナのcredentials
5. EC2のInstance profile credentials

ECS

mojikyo45_640-2.gifc
・ECS Task PlacementはECS with EC2のみ使用可能
3つのタイプ
Binpack - 使用するインスタンスの数を最小にしようとする most cost effective
Ramdom - タスクをランダムに配置する
Spread - インスタンスIDやAZに基づいてタスクを分散配置する
2つの制約
distinctInstance - それぞれのタスクは異なるコンテナインスタンスに配置する
memberOf - クラスタークエリ言語を使用して制約を定義可能     example:t2のインスタンスにのみタスクを配置する

Elastic Beanstalk

image.png
・環境でELB作成したあとはELBのタイプを変えることはできない。
・EB with Docker
シングルDockerの場合、コンテナを実行可能だが、ECSは使用しない。
マルチDockerの場合、ECSを使用する。
•環境のプラットフォームバージョンはコンソールまたはEBCLIから変更できる

CodeCommit

image.png

・AWS SNS、Lambda、CloudWatch Event Rulesを使用して通知が可能。
・クロスアカウントアクセス有

CodeBuild

image.png

・フックの順序
アプリケーションストップ

バンドルをダウンロード

beforeInstall

afterInstall

アプリケーションスタート

サービスの検証
・CodeBuildコンテナは実行終了時に削除される
・Dockerイメージは、プライベートレジストリからのものも使用できる

CodeDeploy

image.png

Appspec.yamlファイルの必須プロパティ
name, alias, currentversion, targetversion

X-Ray

image.png
・BeanStalkでX-Ray デーモンを有効にする方法 - .ebextensions/xray-daemon.configを作成する
•サービスマップは個々の地域のデータではなく全ての地域のデータが表示される
•SQSと統合されている場合、リソースヘッダーはSQSのメッセージサイズ等に影響しない
•サンプリングルールのデフォルト - 1秒あたりに1つのリクエスト、ホスト毎の追加の5%の要求がリクエストされる
・X-Ray SDKが提供するもの -
インターセプター コードに追加して受信 HTTP リクエストをトレースする
クライアントハンドラー アプリケーションが他の AWS サービスの呼び出しに使用する AWS SDK クライアントを計測する
An HTTP クライアント 別の内部および外部 HTTP ウェブサービス呼び出しを計測する

Lambda

image.png

・タイムアウトは最大15分
・環境変数は最大4KB
・FunctionName - 関数の名前
・Layer - Lambda関数実行環境に追加された関数レイヤーのリスト
・Environment - Lambda関数実行中にアクセス可能な変数
・Handler - 関数を実行するために呼び出すコード内のメソッドの名前
・関数をスケジューリングするベストプラクティスはCloudWatch Iventを使用すること

DynamoDB

image.png

・WCU(Write Capacity Units)計算方法例
2KBのオブジェクトを毎秒10個書き込む - 2*10 = 20WCU
*4.5KBのオブジェクトを毎秒6個書き込む - 5
6 = 30WCU
2KBのオブジェクトを毎分120個書き込む - 2(120 / 60) = 4WCU

・RCU(Read Capacity Units)計算方法例
*4KBの10個のアイテムを強力な整合性のある毎秒読み込みで読み込む - (4 / 4) * 10  = 10RCU
*12KBの16個のアイテムを結果整合性のある毎秒読み込みで読み込む - ( 12 / 4 ) * ( 16 / 2 ) = 24RCU
*6KBの10個のアイテムを強力な整合性のある毎秒読み込みで読み込む - ( 8 / 4 ) * 10= 20RCU

・グローバルセカンダリインデックスでは一貫性のある読み取りをサポートしていない
・一貫性のある読み取りをDAXクラスターにリクエストした場合、結果はキャッシュされない
•DynamoDBストリームとLambdaトリガーの併用はベストプラクティス

CloudWatch

image.png
・基本的なモニタリングにアラームを設定する場合 - 少なくとも5分
・詳細なモニタリングにアラームを設定する場合 - 少なくとも1分

S3

image.png
・バケット内のプレフィックスごとに1秒あたり3500のPUTリクエストと、5500のGETリクエストを提供する

IAM

image.png

・バージョニング機能あり
・明示的に拒否するポリシーがある場合、他の許可されたステートメントを上書きする

KMS

image.png
・デフォルト設定では、対象CMKをサポート

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

AWSでUbuntuインスタンスを立ち上げ、指定したバージョンのPythonをソースからインストールする

:dvd: この記事の目的

用途に合わせたPythonの環境をすばやく構築したい、そんな時のための手順を、備忘を兼ねて書き記します。
なお、本記事執筆時点(2021/02/27)での情報となります。AWSのサービス内容は常にアップデートされるため、内容が合致しない場合はご了承下さい。

:dvd: AWSでUbuntuインスタンスを作成

本記事ではAWSの無料利用枠の範囲で設定を行います。
また、AWSのアカウント取得やログインについては割愛します。

1. AWSでインスタンスを選択し設定を行う

(1)AWS画面の左上の「サービス」をクリックし、「コンピューティング」の「EC2」をクリックします。
image.png

(2)左枠メニュー項目から「インスタンス」をクリックします。
image.png

(3)インスタンス一覧画面が表示されたら、右上の「インスタンスを起動」をクリックします。
image.png

(4)まずは、左内枠の「無料利用枠のみ」にチェックします。
image.png

(5)自分の好きなOSを選択して下さい。ここでは、「Ubuntu Server 18.04 LTS」を選択しました。
image.png

(6)「次のステップ:インスタンスの詳細の設定」をクリックします。
無料利用枠の対象であることを確認し、次へ進みます。
image.png

(7)「次のステップ: ストレージの追加」をクリックします。
image.png

(8)「次のステップ: タグの追加」をクリックします。
image.png

(9)「次のステップ: セキュリティグループの設定」をクリックします。
タグは任意で設定して下さい。
image.png

(10)セキュリティグループの設定を行います。
以下赤枠の通り、タイプに「SSH」「HTTPS」の設定を入れます。
ちなみに「カスタムTCP」のポートは"8888"Jupyter Notebookのための設定で、一般的には不要と思います。。
「確認と作成」をクリックします。
image.png

(11)内容を確認し「起動」をクリックします。
image.png

(12)既存のキーペアまたは新しいキーペアを選択し、インスタンスの作成を実行して下さい。
インスタンスの作成手続きは以上です。

2. 必要なビルドツール・ライブラリをインストールする

(1)作成したインスタンスにログインします。
SSHツールを使用し作成したインスタンスへログインします。
ここではTera Termを使用しました。ログインの仕方はAWSの一般的な手順となりますのでここでは省略します。

インスタンスのOSがUbuntuなので、ユーザ名は「ubuntu」となります。
image.png

(2)以下を実行して必要なライブラリをインストールします。

sudo apt update
sudo apt install build-essential libbz2-dev libdb-dev libreadline-dev libffi-dev libgdbm-dev liblzma-dev libncursesw5-dev libsqlite3-dev libssl-dev zlib1g-dev uuid-dev tk-dev

:dvd: バージョンを指定してPythonをソースからインストール

1. Pythonのソースをダウンロードする

(1)Pythonの公式サイトへアクセスします。
AWSインスタンスではなく、ローカルPC端末のブラウザでアクセスします。

:desktop: Python公式サイト
https://www.python.org/

(2)Pythonサイトのメニュー「Download」を選択し、さらに「All releases」をクリックします。
image.png

(3)ソースの一覧から欲しいバージョンのソースを選択しクリックします。
ここでは「Python 3.9.2」を選択しました。
image.png

(4)ソースのURLを取得します。
ソースの選択画面で、欲しいソースのリンクを右クリックしてURLを取得します。
ここでは「Gzipped source tarball」を選択しました。
image.png
右クリックし、「リンクのアドレスをコピー」します。
image.png

(5)AWSインスタンスのプロンプトで、取得したURLからソースをダウンロードします。
プロンプトでは以下のように実行してソースを取得します。

wget https://www.python.org/ftp/python/3.9.2/Python-3.9.2.tgz

実行するとソースをダウンロードします。
image.png

(6)ソースを解凍します。
取得したファイルを以下のコマンドで解凍します。

tar -xvf Python-3.9.2.tgz

2. Pythonをインストールする

(1)解凍後、以下のコマンドでPythonフォルダへ移動します。

cd Python-3.9.2/

(2)以下の各コマンドを順次実行し、インストールを行います。

./configure
make
sudo make install

途中でエラーが発生せず、最後に「Successfully ...」と表示されたら完了です。
image.png

(3)pipを使用するため、以下のコマンドでpipライブラリをインストールします。

sudo apt install python-pip

(4)以下のコマンドでPythonのバージョンを確認します。
想定通りのバージョンが表示されればOKです。

./python -V

image.png

(5)最後に、pythonコマンドのパスを設定します。
ここではインストールしたpythonのディレクトリが以下ですので、
image.png

次のようにコマンドを実行します。

sudo ln -s /home/ubuntu/Python-3.9.2/python /usr/bin/python

上書きエラーが出る場合は、強制実行オプション[-f]を付けて実行します。

以上でインストールは完了です。
Pythonコマンドでバージョンの確認を行って下さい。

:dvd: 関連情報

:dvd: ご意見など

ご意見、間違い訂正などございましたらお寄せ下さい。

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

Clamav の memory allocation エラーの対処方法

# clamdscan --multiscan / 

上記コマンドで、/swapfile の memory allocation エラーとなる。

/swapfile: Can't allocate memory ERROR

これは別に問題ない。/swapfile が物理メモリの値を超えているため。
/swapfile を小さくするのは意味がないので、検査対象から外す。

# vim /etc/clamd.d/scan.conf

...(snip)...
ExcludePath ^/proc/
ExcludePath ^/sys/
# add this line
ExcludePath ^/swapfile
...(snip)...

デーモンリスタート

# systemctl restart clamd@scan.service
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【AWS CloudFormation】「レプリケーション時間のコントロール (RTC)」等を含めたS3レプリケーション設定のテンプレートの書き方

前置き

現場で AWS の CloudFormation に初挑戦することになりました。
「レプリケーション時間のコントロール (RTC)」等を含めた、
S3レプリケーション設定のテンプレートの書き方で引っかかったことがあったので、投稿します。

内容

以下のようなテンプレートファイルでスタックを作成し、実行しました。

S3BucketOriginal:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub 'original-${AWS::AccountId}'
      VersioningConfiguration:
        Status: Enabled
      ReplicationConfiguration:
        Role: !Sub 'arn:aws:iam::${AWS::AccountId}:role/OriginBucketBackupRole'
        Rules:
        - Id: BackUpRule
          Status: Enabled
          DeleteMarkerReplication:
            Status: Disabled
          Destination:
            Bucket: !Sub 'arn:aws:s3:::backup-${AWS::AccountId}'
            EncryptionConfiguration:
              ReplicaKmsKeyID: !Sub 'arn:aws:kms:${AWS::Region}:${AWS::AccountId}:alias/BackupKey'
            Metrics: 
              Status: Enabled
              EventThreshold: 
                Minutes: 15
            ReplicationTime:
              Status: Enabled
              Time:
                Minutes: 15
          SourceSelectionCriteria: 
            SseKmsEncryptedObjects:
              Status: Enabled

すると、以下のようなエラーになってしまいました。

DeleteMarkerReplication cannot be used for this version of Cross Region Replication configuration schema.
Please refer to S3 Developer Guide for more information

このエラーは、以下CloudFormationのユーザーガイド「ReplicationRule」内の注記のとおりです。

前述のエラーメッセージ内のthis versionは、S3のレプリケーション設定のバージョンのことでした。

スクリーンショット 2021-02-28 6.25.49.png

以下のように、Filter を設定しないとS3のレプリケーション設定はV1になってしまうということでした。

・・・
      ReplicationConfiguration:
        Role: !Sub 'arn:aws:iam::${AWS::AccountId}:role/OriginBucketBackupRole'
        Rules:
        - Id: BackUpRule
          Status: Enabled
          DeleteMarkerReplication:
             Status: Disabled
          Filter:         # 追加
            Prefix: ""    # 追加
・・・

Filter を設定していないと、以下のように Replication Time Control (S3 RTC) の設定もエラーとなってしまいます。

ReplicationTime cannot be used for this version of the replication configuration schema.
Please refer to S3 Developer Guide for more information

Filter を設定したテンプレートでスタックを実行して今度こそ・・・
と思ったら以下のようなエラーになってしまいました。

Priority must be specified for this version of Cross Region Replication configuration schema.
Please refer to the S3 Developer Guide for more information

Priority(優先度)は、 CloudFormation のユーザーガイドでは「必須:いいえ」となっているのに・・・

スクリーンショット 2021-02-28 6.27.47.png

マネジメントコンソールからの設定だと、自動に設定してくれていれるんですね・・・
全く意識したことなかったです・・・

スクリーンショット 2021-02-28 6.52.27.png

その他にも、レプリケーション時間のコントロール (RTC)等の設定はマネジメントコンソール上だとチェックボックスでONにするだけなのですが、
CloudFormationのテンプレートでは時間数が必須入力する必要があります。
このあたり、マネジメントコンソールでは意識しないので難しい・・・

スクリーンショット 2021-02-28 6.54.54.png

            Metrics: 
              Status: Enabled
              EventThreshold: 
                Minutes: 15
            ReplicationTime:
              Status: Enabled
              Time:
                Minutes: 15

結果

最終的にはテンプレートファイルを以下のようにすることで、レプリケーション設定を作成することが出来ました。
(BucketName、ReplicaKmsKeyID 辺りの指定方法は適宜置き換えください)

Resources:
  S3BucketOriginal:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub 'original-${AWS::AccountId}'
      VersioningConfiguration:
        Status: Enabled
      ReplicationConfiguration:
        Role: !Sub 'arn:aws:iam::${AWS::AccountId}:role/OriginBucketBackupRole'
        Rules:
        - Id: BackUpRule
          Status: Enabled
          DeleteMarkerReplication:
             Status: Disabled
          Filter:       #追加
            Prefix: ""  #追加
          Priority: 0   #追加
          Destination:
            Bucket: !Sub 'arn:aws:s3:::backup-${AWS::AccountId}'
            EncryptionConfiguration:
              ReplicaKmsKeyID: !Sub 'arn:aws:kms:${AWS::Region}:${AWS::AccountId}:alias/BackupKey'
            Metrics: 
              Status: Enabled
              EventThreshold: 
                Minutes: 15
            ReplicationTime:
              Status: Enabled
              Time:
                Minutes: 15
          SourceSelectionCriteria: 
            SseKmsEncryptedObjects:
              Status: Enabled
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS初期設定メモ(2021/02/28時点)

IAMで作業用ユーザーを作成

省略。

二要素認証の設定

省略。

CloudWatchで料金アラートを設定

  1. 右上のユーザー名からトグルを開き、「マイ請求ダッシュボード」を選択。
    image.png

  2. サイドメニューの「Billingの設定」を選択し、画像の通りに設定する。
    image.png

  3. 上記画像の「請求アラートを管理する」リンクを選択。

  4. CloudWatchの画面が表示されるので、サイドメニューから「請求」を選択。

  5. 「アラームの作成」ボタンをクリック。

  6. 条件を設定する画面になるので、閾値を設定し、アラームを作成する。
    (ここでは50 USDを超えた場合に設定し、それ以外はデフォルトで登録)
    image.png

CloudTrailで操作ログをS3に保存する

1.メニューから CloudTrailの画面に遷移し、「証跡の作成」を選択。
image.png

  1. 「証跡名」を設定して作成
    1. マルチリージョンの証跡が有効になっていることを確認する。
    2. S3バケット上に保存される設定になっていることを確認する。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

インフラエンジニア見習いによるAWS認定ソリューションアーキテクトアソシエイト(SAA-C02)合格体験記

始めに

皆さま、こんにちは。
スプラトゥーン3の発表に涙が止まらなかったハイカラスクエアのタコの@hiroki_tanakaです。
先日、AWS認定資格の1つであるAWS認定ソリューションアーキテクトアソシエイトを受験したので、
振り返りの意味も込めて試験までの道のりをご紹介させていただきます。

自己紹介

  • 社会人歴6年目エンジニア
  • 得意領域はサーバサイドのアプリケーションレイヤ(RubyとJavaが好き)
  • 2020年10月頃からインフラエンジニアに入門した。
  • AWSはEC2やS3・VPCの存在を知っているが、自身で一から構築したことはない。
  • 2020年11月にAWS認定クラウドプラクティショナーに合格した。(その時の話はこちらの記事を参照ください。)

AWS認定ソリューションアーキテクトアソシエイトとは

AWS認定ソリューションアーキテクトアソシエイトとは、公式サイトにあるようにAWSの分散システムの可用性・コスト効率・高耐障害性及びスケーラビリティの設計に関する1年以上の実務経験を持つAWS設計者を対象にしている資格です。
1年以上の実務経験者を対象にしている部分からわかるようにAWSの12ある資格の中では中堅にあたる資格です。
(巷では、AWS認定資格の登竜門的な位置付けを受けているようです。)

そのため、個々のAWSサービスの理解は勿論必要なのですが、それだけでなく、
- 複数のAWSのサービスを使用して、安全で堅牢なアプリケーションを構築・デプロイするための知識
- 顧客の要件に基づきながら、AWSのアーキテクチャ設計原則に沿ってのシステム構築のノウハウ
といった可用性・コスト効率・耐障害性・スケーラビリティの4点について各システムでの最適な構成を理解していることが求められます。

つまり、この資格を取得すると顧客のニーズを満たすAWS環境構築の設計・実装を行うための入り口に立てます。
クラウドプラクティショナーを取得した私にとって、次のステップとしてまさにピッタリの資格でした。

image.png

ちなみに、
試験時間は130分で問題数は65問・最低合格点は720点(最低点数100点の1000点満点)となっています。
受験料は15,000円(税別)になります。
(ただ、私はクラウドプラクティショナーの合格特典で次の資格試験の受験料を50%OFFで受験することが出来ました。)

勉強法

勉強期間は12月~2月までの3ヶ月間なのですが、途中サボっていた期間もあるので実質2ヶ月程度です。
基本的には平日の夜に30~1時間でしたが、受験直前は1日2〜3時間は勉強していました。

参考書

これまでの自身の学習経験から私は参考書と紙とペンを用いた勉強方法が一番性に合っていると分かっていたので、
こちらのソリューションアーキテクトアソシエイトの参考書を紙に書き出しながら1周し、各AWSサービスの概要・使い方を頭に入れました。
その後、各章の練習問題・章末の模擬問題を4周しました。
問題を解く際は正解だけでなく、不正解の選択肢に関しても何故不正解なのか・どう直したら正解にできるのかを考えるようにしていました。

Udemyの模擬試験

上記の参考書では問題数が少なかったので、模擬問題集Udemyで購入し参考書で身につけた知識の確認を行いました。
(問題集の定価は12,000円なのですが、Udemyは頻繁にセールを行っているのでそこで購入しました。1610円で買うことが出来ました。)
この模擬問題集は非常に難易度が高く、実際の試験よりも難しく感じました。
そのため、この模擬問題集でしっかり合格点を出せるようになれば、高い確率で合格できると思います。

1周目時点で私の正答率は50%程度でかなり焦ったのですが、落ち着いて1周目で間違えた問題の解説を頭に入れる→問題を解く→正誤問わず、気になった問題の解説を頭に入れる→問題を解くということを繰り返し行いました。
最終的には正答率は95~98%で安定し、安心して本試験に臨むことができました。

AWSの模擬試験

AWSが提供している2000円(税抜)で受けられる問題数20問の模擬試験があるのですが、クラウドプラクティショナーの合格特典で無料で受験できるため受験しました。
ただ、こちらは本試験よりも難易度が低いことや受験結果の点数のみ送られてくるため、自分がどこを間違えたのかわからず復習ができません。
そのため、受けなくても良かったかな…と今は思っています。

受験と結果

クラウドプラクティショナーの時同様に、コロナ禍のため自宅でのオンライン受験かテストセンターでのオフライン受験を選択できるのですが、自宅での受験はデスクだけでなく周りの壁や棚も整理する必要があることやそもそも自宅ではあまり集中できない可能性を考えて最寄りのテストセンターで受験しました。
また余談ですが、オンライン受験をする場合はPSIピアゾンVUEのどちらかを選択できるのですが、日本語対応しているピアゾンVUEがオススメです。
PSIは試験官とのやり取りが全て英語のチャットで行われるため、日常会話程度の英語が出来ないと少しつらいかと思います。

本試験はUdemyの模擬問題よりも難易度は低く感じたためか比較的スムーズに解答でき、試験時間130分のうち60分で1周することが出来ました。
そのため、一問一問落ち着いて見直し対応することができました。
(見直しは2周くらいしました。)
問題自体は私の場合は、CloudFront・EC2のAutoScaling・システム移行に関する問題が多かったです。

試験後の簡単なアンケートに答えて、表示された結果は………
無事合格!

image.png

ただ、試験中は「この感じなら900点は固いはず」と思って解答していたので、正直もっと点数が取れていると思っていました。
775点は合格ラインが720点なので、中々ギリギリです…笑

とはいえ、合格できて本当に良かったです!(=^▽^=)

終わりに

正直、ソリューションアーキテクトアソシエイトはクラウドプラクティショナーから難易度が一気に上がり、途中勉強のモチベーションが下がった時期もありました。
(Udemyの模擬試験の1回目を終えた時はあまりの点数の取れなさに「これは受験を止めた方がいいんじゃないか…?」と悩みました笑)
ただ、勉強を通じてAWSのことを知れるのは本当に楽しく、合格まで走りきった時はより一層AWSのことが好きになりました.
次は同じAWS認定のアソシエイト資格であるSysOpsアドミニストレーターアソシエイトデベロッパーアソシエイトを取得したいと思います。
目指せ、AWSアソシエイト資格三冠王!ヾ(`・ω・´)ゞ

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