日々是クラウド

AWSで色々やってみるブログ

CodePipelineとPytestで自動テスト

別にPythonじゃなくてCでもC++でもいいんだけど、機械学習屋さんとしてはPythonに慣れ親しんでいるので、その例でトライ。 HelloWorldで書いても良かったけど、せっかくなのでgit pushすると、自動でPytestが動くようなもの。

CodeCommitとPytestとbuildファイルの準備

手元のgit環境の準備

CodeCommitにリポジトリを作成して、自在にgit clone, git pushできる環境を作っておく。 EC2にIAMロールを設定してやるのが一番シンプル。

適切なPythonコードの準備

ディレクトリ構成はこんな感じ。今回はtest_1.pyじゃなくて、test_str.pyという名前にする。
f:id:yohei_ok:20210219130901p:plain

test_str.pyは、すごく簡単なテストを置く。

def test_str():
    assert 'a' == 'a'
    assert 'b' == 'b'

buildspec.yamlファイルの準備

こんなファイルを準備。

version: 0.2
 
phases:
  install:
    runtime-versions:
      python: 3.8
    commands:
      - pip3 install pytest
  build:
    commands:
      - echo test
      - pytest --junit-xml=reports/unittest_results.xml

reports:
  pytest_reports:
    files:
      - unittest_results.xml
    base-directory: reports
    file-format: JUNITXML
  • python3.8を使いますという宣言
  • テストをしたいのでpytestをインストール
  • pytestコマンドの実行 オプションとして、レポートファイルを出力

ちなみにpytestはフォーマットに従っていれば、ディレクトリなどを指定しなくても自動でテストファイルを探してテストしてくれる。 buildspec.yamlは下記のようなディレクトリ構成位置に置く。
f:id:yohei_ok:20210219130728p:plain

CodeBuildの準備

ビルドプロジェクトの作成

マネジメントコンソールから、下記の設定で作っていく。

  • プロジェクト名:任意
  • ソース:CodeCommit
  • リポジトリ:既に作ってあるリポジトリ
  • ビルド対象のブランチ:今回はmaster

f:id:yohei_ok:20210219114526p:plain

環境についてはpython機械学習ならUbuntuを選んでおくと良い気がする。確信はない。
(本来は環境がちゃんと用意されたDockerイメージを用意したほうが良いと思う。それは今度勉強する予定。)
ランタイムはStandardでとりあえず動く。 f:id:yohei_ok:20210219114941p:plain

Buildspecは前項で作ったファイルを使うので、buildspecファイルを使用する。を選択。 これでビルドプロジェクトを作成。

CodePipelineの準備

プロジェクト名を任意で付与して作る。

ソースステージ

ビルドステージ

  • プロバイダー:AWS CodeBuild
  • リージョン:アジア・パシフィック(東京)
  • プロジェクト名:CodeBuildの準備で作成したプロジェクト名 残りはデフォルト。 f:id:yohei_ok:20210219125530p:plain

デプロイステージ

今回は自動テストまでをスコープとするので、導入段階をスキップをクリック。 以上でパイプラインを作成できる。

自動テスト

恐らくパイプラインを作っただけで、1回目のパイプラインが流れる、と思う。 テストはエラーが起きないようなテストなので、成功するはず。

以降、コードをPUSHするたびに自動的にパイプラインが流れ、自動テストが実行される。 実行結果はCodeBuildのビルド履歴やレポート履歴で見ることが可能。

IAMロールを使ってCodeCommitからクローンする

EC2を使って、CodeCommitからリポジトリをクローンしたい場合、 credential情報を使わずに、IAMロールを使うことが可能。

IAMロールの設定

EC2にCodeCommitのポリシーをつける。 とりあえずFullAccessを付与。ReadOnlyなどは適宜設定。 f:id:yohei_ok:20210219094206p:plain

gitの設定

gitのcredentialのhelperとして、awsコマンドを用いるように設定する。

$ git config --global credential.helper '!aws --region ap-northeast-1 codecommit credential-helper $@'
$ git config --global credential.UseHttpPath true

cloneする

SSHでEC2に入った状態で、下記コマンドで普通に取得可能

$ git clone http://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/REPOSITORY NAME

EC2からcloneするなら、アクセスキーなどを使用しないほうが圧倒的にスマート。

EC2とDockerで手軽にJenkinsを試す #1

第1回目はEC2へdockerをセットアップし、JenkinsのイメージをRunする。
Jenkinsの動作確認までをしていきたい。 第2回目でCodeシリーズあたりと連携をしていきたいと思う。

EC2へのDockerのインストール

そこら中に記事があるので、そちらを参照。

Jenkinsのセットアップ

jenkins blueoceanのダウンロード

docker pull jenkinsci/blueocean

Jenkins用のディレクトリ作成

mkdir ~/docker/jenkins

Imageの起動

こちらのサイトを多分に参考にさせて頂きました。

sudo docker run \         
-u root \
--rm \
-d \
-p 8080:8080 \
-v $HOME/docker/jenkins:/var/jenkins_home \
-v /var/run/docker.sock:/var/run/docker.sock \
jenkinsci/blueocean

一行版。

sudo docker run -u root --rm -d -p 8080:8080 -v $HOME/docker/jenkins:/var/jenkins_home -v /var/run/docker.sock:/var/run/docker.sock jenkinsci/blueocean

Web画面でのJenkinsのセットアップ

上記設定で起動すれば、http://EC2-public-IP:8080にアクセスすると、初期画面が出る。 初期画面ではパスワードを入れろと言われるので、下記にあるパスワードを調べる。

cat ~/docker/jenkins/secrets/initialAdminPassword

入力してあげると、こんな画面が出る。
f:id:yohei_ok:20210217145024p:plain

Install suggested pluginを選ぶと、インストールが進む。
f:id:yohei_ok:20210217145105p:plain

インストールが終わると、初期ユーザー登録画面が出てくる。 ユーザ名やパスワードなどを登録する。 f:id:yohei_ok:20210217145135p:plain

わーい。
f:id:yohei_ok:20210217145332p:plain

Adminでログインになった場合

initial passwordを使ってLoginしたところ、Adminでログインになることがある。 その場合は、jenkinsの管理→ユーザーの管理からユーザーを作成しておけばOK。

Jenkinsの動作確認

まずは新規ジョブの作成→フリースタイル・プロジェクトのビルドを選ぶ。 f:id:yohei_ok:20210217190020p:plain

ビルド手順の追加→シェルの実行を選択。 f:id:yohei_ok:20210217190512p:plain

サンプルとして、Hello Worldの表示をテストしてみる。 下記のように入力し、保存。 f:id:yohei_ok:20210217191708p:plain

ビルドを実行。ビルド履歴を確認。 f:id:yohei_ok:20210217190737p:plain f:id:yohei_ok:20210217190944p:plain

コンソール出力を見ると、hello worldが出力されて、 エラーなくテストが成功していることがわかる。 f:id:yohei_ok:20210217191640p:plain ちなみに&JOB_NAMEはtestという新規ジョブを作ったため、その名前が出力されている。

付録(Dockerの便利コマンド)

# Imageのダウンロード
docker pull <Image>

# コンテナの一覧表示
docker ps -a

# イメージの一覧表示
docker images

# コンテナの削除
docker rm <container-ID>

# イメージの削除
docker rmi <image-ID>

CloudFormationで簡単に機械学習の開発環境を整える

はじめに

ちょっと検討をしてみようと思うたびに、VPC作ってSubnet作って、InternetGatewayとElasticIP取得して、NatGateway設置して・・
と単純なprivate, publicサブネットを置くだけでも何度もやると面倒くさい。

特に機械学習開発環境代わりに使うような場合は、複数AZにまたがるような可用性云々じゃなくて、検討用土台をさくっと作りたい。
でもSageMakerのNotebookインスタンスじゃ物足りないという人がいると思う。

構成図

前回も紹介したこれを自動で作ることにする。
●ポイント

  • 踏み台サーバーでセキュリティ対策
  • NATゲートウェイでPrivate環境でもソフトを更新可能
  • さくっと検討用に使いたいのでOneZone構成
  • 自分の練習用 f:id:yohei_ok:20210205194019p:plain

はまりどころ

いろいろハマったけど、下記はその一例。参考になれば。

NATGatewayでのElasticIPの指定

IPアドレスではなくIDで指定するため、下記のように"Fn::GetAtt::"を使って指定している。

  NATGateway:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId:
        Fn::GetAtt:
          - ElasticIP
          - AllocationId

NATGatewayへのRoute

RouteでGatewayIdではなく、NATGatewayIdを使う必要あり。

  PrivateRoute: 
    Type: AWS::EC2::Route
    Properties: 
      NatGatewayId: !Ref NATGateway

EC2のSecurityGroupの設定はリストで

SecurityGroupIdsのように、複数形になっているのでリストで渡すことをお忘れなく。

  PrivateInstance:
    Type: AWS::EC2::Instance
    Properties: 
      SecurityGroupIds: 
        - !Ref PrivateSecurityGroup

yamlファイル全体はこちら

これをcloud formationに入れることで一発で踏み台サーバーと検討用のprivateサーバーを起動することが可能。 インスタンスのファミリーとかサイズは適宜変えるとよい。

AWSTemplateFormatVersion: "2010-09-09"
Description: 
  OneZone VPC/Subnet/EC2 Create

Parameters:
  # 踏み台サーバーはAmazonLinux & t2.microで簡素に
  BationHostImageId:
    Type: AWS::SSM::Parameter::Value<String>
    Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2
  
  BationHostInstanceType:
    Type: String
    Default: t2.micro

  # 機械学習用インスタンスはDeepLearning AMIを使って、実験用と高機能用などを用意
  PrivateImageId:
    Type: String
    Default: ami-088585cfb750459af

  PrivateInstanceType:
    Type: String
    Default: m5.2xlarge
    AllowedValues : ["t2.micro", "m5.2xlarge"]

  # SSHでつなぐための接続元IP(自分のIP)
  MyIP:
    Description: IP address allowed to access EC2
    Type: String
    Default: 0.0.0.0/32

  # SSH用のKeyはすでにある前提
  KeyName:
    Description: The EC2 Key Pair to allow SSH access to the instance
    Type: "AWS::EC2::KeyPair::KeyName"
    Default: .pem

Resources:
# #####################################
# VPC & IG
# #####################################
  VPC:
    Type: AWS::EC2::VPC
    Properties: 
      CidrBlock: 10.5.0.0/16
      EnableDnsHostnames: false
      EnableDnsSupport: true
      InstanceTenancy: default
      Tags: 
        - Key: Name
          Value: my-vpc-stack

  InternetGateway: 
    Type: AWS::EC2::InternetGateway
    Properties: 
      Tags: 
        - Key: Name
          Value: my-ig-stack

  InternetGatewayAttachment: 
    Type: AWS::EC2::VPCGatewayAttachment
    Properties: 
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC


# #####################################
# Subnet & NAT
# #####################################
  PublicSubnet:
    Type: AWS::EC2::Subnet
    Properties: 
      AvailabilityZone: "ap-northeast-1a"
      CidrBlock: 10.5.0.0/24
      MapPublicIpOnLaunch: true
      Tags: 
        - Key: Name
          Value: public-subnet-stack
      VpcId: !Ref VPC

  PrivateSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: "ap-northeast-1a"
      CidrBlock: 10.5.1.0/24
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: private-subnet-stack
      VpcId: !Ref VPC

  NatIP:
    Type: AWS::EC2::EIP
    Properties: 
      Domain: vpc
      Tags: 
        - Key: Name
          Value: nat-eip-stack

  NATGateway:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId:
        Fn::GetAtt:
          - NatIP
          - AllocationId

      SubnetId: !Ref PublicSubnet
      Tags:
        - Key: Name
          Value: my-nat-stack

# #####################################
# RouteTable & Routing
# #####################################
  PublicRouteTable: 
    Type: AWS::EC2::RouteTable
    Properties: 
      VpcId: !Ref VPC 
      Tags: 
        - Key: Name
          Value: rt-public-stack

  PublicRoute: 
    Type: AWS::EC2::Route
    Properties: 
      RouteTableId: !Ref PublicRouteTable 
      DestinationCidrBlock: "0.0.0.0/0"
      GatewayId: !Ref InternetGateway 

  PublicRouteAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet

  PrivateRouteTable:
    Type: AWS::EC2::RouteTable
    Properties: 
      VpcId: !Ref VPC 
      Tags: 
        - Key: Name
          Value: rt-private-stack

  PrivateRoute: 
    Type: AWS::EC2::Route
    Properties: 
      RouteTableId: !Ref PrivateRouteTable 
      DestinationCidrBlock: "0.0.0.0/0"
      NatGatewayId: !Ref NATGateway

  PrivateRouteAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable
      SubnetId: !Ref PrivateSubnet

# #####################################
# SecurityGroup
# #####################################
  PublicSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
        GroupDescription: Allow SSH to BationHost
        GroupName: allow-ssh-to-bation-stack
        VpcId: !Ref VPC
        SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: !Ref MyIP

  PrivateSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
        GroupDescription: Allow SSH from bation
        GroupName: allow-ssh-from-bation-stack
        VpcId: !Ref VPC
        SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          SourceSecurityGroupId: !Ref PublicSecurityGroup

# #####################################
# EC2
# #####################################
  BationHost: 
    Type: AWS::EC2::Instance
    Properties: 
      ImageId: !Ref BationHostImageId
      InstanceType: !Ref BationHostInstanceType
      KeyName: !Ref KeyName
      NetworkInterfaces: 
        - AssociatePublicIpAddress: "true"
          DeviceIndex: "0"
          SubnetId: !Ref PublicSubnet
          GroupSet:
            - !Ref PublicSecurityGroup
      Tags:
        - Key: Name
          Value: bation-host-stack

  EC2IP:
    Type: AWS::EC2::EIP
    Properties:
      InstanceId: !Ref BationHost
      Tags: 
        - Key: Name
          Value: ec2-eip-stack

  PrivateInstance:
    Type: AWS::EC2::Instance
    Properties: 
      ImageId: !Ref PrivateImageId
      InstanceType: !Ref PrivateInstanceType
      KeyName: !Ref KeyName
      SubnetId: !Ref PrivateSubnet
      SecurityGroupIds: 
        - !Ref PrivateSecurityGroup
      Tags:
        - Key: Name
          Value: private-instance-stack

# SSH接続用にPublicとPrivateのIPを出力しておくと便利
Outputs:
  PublicIP:
    Value: !GetAtt BationHost.PublicIp
    Description: Public IP of BationHost

  PrivateIP:
    Value: !GetAtt PrivateInstance.PrivateIp
    Description: Private IP of EC2 instance

AWSアカウント間でのCodeCommitからのClone

AWSアカウントAのリポジトリからAWSアカウントBの人がCloneする

Bの人がAのアクセスキーIDとシークレットアクセスキーを持っている場合

非常に簡単。 AWSアカウントBの人のEC2で、下記コマンドでAWSアカウントAのcredential情報を入力。 credentialを入れるので、できればprivate subnetで踏み台サーバー経由が良いと思う。

aws config --profile <ENVNAME>

その上で下記コマンドでリポジトリをCloneできる。

git clone codecommit://<ENVNAME>@<repository_name>

Git認証情報がある場合

普通にネット経由でできます。 前回と同様の操作で、HTTPS経由でCloneできると思います。

CodeCommitからリポジトリをクローンする

IAMユーザーから認証情報を作成

AWS CodeCommitのHTTPS Git認証情報を生成をクリック。 f:id:yohei_ok:20210212185614p:plain

f:id:yohei_ok:20210212185802p:plain

ユーザー名とパスワードをダウンロードしておく。

Git cloneするだけ

git clone https://git-codecommit.ap-northeast-1.amazonaws.com/*********/

こうするとユーザ名とパスワードを聞かれるので、 先程ダウンロードしておいたユーザ名とパスワードを使えば良い。

毎回Credential情報入力するのが面倒な方へ

MaxOSXの場合は下記コマンドを使うと、OSXのキー保存機能で保存してくれる。 細かく知りたい方は別途検索すると良い。

git config --global credential.helper osxkeychain

VSCODEから踏み台サーバー越しにSSH接続をする

多段SSHしたい

機械学習の研究開発用だと、SageMakerのJupyter NoteBookじゃ物足りず、EC2にSSHで入って開発したくなる。僕の使い方が悪いのか・・?開発用にEC2を置くなら、Privateに開発用、Publicに踏み台サーバーを置くといいかなぁと思う。(特に会社とかで使う場合)

要はこういう状態 。 f:id:yohei_ok:20210205194019p:plain

SSH config

VSCODESSHのconfigには下記を書けばOK。 どちらの.pemファイルもローカルのファイルを指定すれば良い。 publicEC2に鍵を置かなくて良い点が安心感。

Host public
  HostName PUBLIC EC2のIP
  User ec2-user
  IdentityFile ****.pem(フルパスで書いたほうが良い)

Host private
  Hostname PRIVATE EC2のIP
  User ec2-user
  IdentityFile ****.pem(フルパスで書いたほうが良い。)
  ProxyCommand ssh -W %h:%p public

VSCODE

あとは直接privateのほうを開くだけ。 f:id:yohei_ok:20210205194705p:plain

EC2の設定どうする

SSHフルオープンが心配な場合は、Public EC2のセキュリティグループでインバウンドで自分のIPを指定すると良い。 さらにPrivate EC2のセキュリティグループのインバウンドに、Public EC2に設定してあるセキュリティグループを設定してあげればこちらも安心。