- 投稿日:2020-11-18T23:44:29+09:00
AWS Fargate使ってみた
こんにちは。むんです
AWS FargateでDockerを動かしてみたので、備忘録です。目次
- AWS Fargateとは?
- Fargateでコンテナを作る
- まとめ
AWS Fargateとは?
端的に言うと、サーバーレスでコンテナが使える技術。
Fargateでコンテナを作る
AWS公式チュートリアル
https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/userguide/fargate-getting-started.html1. Amazon ECS コンソールの初回実行ウィザードを開く
2. ナビゲーションバーから、米国東部(バージニア北部) リージョンを選択する
現在、Fargateは米国東部(バージニア北部) リージョンでのみ、使える機能なようです。
3. コンテナを選択する
4. Serviceを選択
Load Balancerは今回起動してみたいだけなので、Noneを選択。
5. Clusterを選択
Cluster nameも特にこだわらないので、defaultのままでNext。
6. 確認、起動
7. ダッシュボードを確認する
「View Service」を選択。
下記のような画面が表示される。
赤枠のTaskをクリック。
ENI Idをクリック。
Public IP4 addressが記載されていることを確認する。
8. ブラウザにて確認
ブラウザにPublic IP4 addressを入力する。
起動できてますね!まとめ
AWS Forgateを使用してみました。
ちょっと色々まだ謎なままですが・・・。
これからもう少しいじってみたいと思います。
- 投稿日:2020-11-18T23:26:20+09:00
CloudFormationで、GetAZs で取得できるAZについて
- 投稿日:2020-11-18T22:59:07+09:00
俺でもわかるトレースID(AWS CloudFrontとAWS ELB)
俺です
いつでもCFやELBぶっ刺してもばっちこいなNginxになれるようにlog_formatに脳死でいれとけメモです
HTTPヘッダ Nginx log_format X-Amz-Cf-Id $http_x_amz_cf_id X-Amzn-Trace-Id $http_x_amzn_trace_id https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/RequestAndResponseBehaviorCustomOrigin.html
https://aws.amazon.com/jp/premiumsupport/knowledge-center/trace-elb-x-amzn-trace-id/死
- 投稿日:2020-11-18T17:30:45+09:00
Go で AWS のプライベート VPC リソースにアクセスする
やりたいこと
AWS Systems Manager 経由で SSH トンネルを使用してプライベート VPC リソースにアクセスしたいと考えています。どうすればよいですか?
こちらの記事で紹介されているように、AWS Systems Manager Session Manager を利用することで、VPC 内に用意した踏み台サーバ経由でプライベート VPC 内のリソース (RDS など) にアクセスすることができます。
本来は SSH Client と AWS CLI を組み合わせて SSH トンネリング (ポートフォワーディング) を行うのですが、今回はこれを Go 言語と aws-sdk-go でやってみようと思います。
これにより、いちいちssh
コマンドを叩かずにプライベート VPC リソースに対してプログラムを実行できるのでかっこいいです(たぶん)。検証環境
VPC 内の Private Subnet に RDS インスタンス (MySQL) と踏み台用の EC2 インスタンスがある環境を想定します。
図のようにローカル PC から Session Manager 経由で踏み台サーバに接続し、最終的にプライベートな RDS インスタンスに対して
SHOW DATABASES
を実行することをゴールとします。上記の検証環境を再現する CloudFormation テンプレートを用意したのでお手元で試したい方は下記の詳細をご参照ください。
詳細
次のテンプレートを使用して CloudFormation スタックを作成すると検証環境を作成できます(
ap-northeast-1
限定)。AWSTemplateFormatVersion: 2010-09-09 Description: Create private RDS and bastion instance in VPC Metadata: AWS::CloudFormation::Interface: ParameterGroups: - Label: default: VPC Configuration Parameters: - VPCCIDR - PrivateSubnetACIDR - PrivateSubnetCCIDR - Label: default: DB Configuration Parameters: - DBMasterUsername - DBMasterPassword - Label: default: Bastion Configuration Parameters: - BastionKeyPair - BastionImageId ParameterLabels: VPCCIDR: default: VPC CIDR PrivateSubnetACIDR: default: Private Subnet A CIDR PrivateSubnetCCIDR: default: Private Subnet C CIDR DBMasterUsername: default: Database Master Username DBMasterPassword: default: Database Master Password BastionKeyPair: default: Bastion Server Key Pair Name BastionImageId: default: Bastion Server Image ID (DO NOT CHANGE) Parameters: VPCCIDR: Type: String Default: 10.1.0.0/24 PrivateSubnetACIDR: Type: String Default: 10.1.0.1/26 PrivateSubnetCCIDR: Type: String Default: 10.1.0.64/26 DBMasterUsername: Type: String Default: root DBMasterPassword: Type: String BastionKeyPair: Type: String BastionImageId: Type: AWS::SSM::Parameter::Value<String> Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2 Resources: VPC: Type: AWS::EC2::VPC Properties: CidrBlock: !Ref VPCCIDR EnableDnsSupport: true EnableDnsHostnames: true InstanceTenancy: default Tags: - Key: Name Value: ssm-bastion-example-vpc PrivateSubnetA: Type: AWS::EC2::Subnet Properties: AvailabilityZone: ap-northeast-1a CidrBlock: !Ref PrivateSubnetACIDR VpcId: !Ref VPC Tags: - Key: Name Value: ssm-bastion-example-private-subnet-a PrivateSubnetC: Type: AWS::EC2::Subnet Properties: AvailabilityZone: ap-northeast-1c CidrBlock: !Ref PrivateSubnetCCIDR VpcId: !Ref VPC Tags: - Key: Name Value: ssm-bastion-example-private-subnet-c PrivateRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC Tags: - Key: Name Value: ssm-bastion-example-private-route PrivateSubnetRouteTableAssociationA: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PrivateSubnetA RouteTableId: !Ref PrivateRouteTable PrivateSubnetRouteTableAssociationC: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PrivateSubnetC RouteTableId: !Ref PrivateRouteTable VPCEndpointSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Serucity group for vpc endpoint VpcId: !Ref VPC SecurityGroupIngress: - IpProtocol: tcp FromPort: 443 ToPort: 443 CidrIp: !Ref VPCCIDR VPCEndpointSSM: Type: AWS::EC2::VPCEndpoint Properties: ServiceName: com.amazonaws.ap-northeast-1.ssm VpcEndpointType: Interface VpcId: !Ref VPC SubnetIds: - !Ref PrivateSubnetA SecurityGroupIds: - !Ref VPCEndpointSecurityGroup PrivateDnsEnabled: true VPCEndpointSSMMessages: Type: AWS::EC2::VPCEndpoint Properties: ServiceName: com.amazonaws.ap-northeast-1.ssmmessages VpcEndpointType: Interface VpcId: !Ref VPC SecurityGroupIds: - !Ref VPCEndpointSecurityGroup SubnetIds: - !Ref PrivateSubnetA PrivateDnsEnabled: true VPCEndpointEC2Messages: Type: AWS::EC2::VPCEndpoint Properties: ServiceName: com.amazonaws.ap-northeast-1.ec2messages VpcEndpointType: Interface VpcId: !Ref VPC SecurityGroupIds: - !Ref VPCEndpointSecurityGroup SubnetIds: - !Ref PrivateSubnetA PrivateDnsEnabled: true VPCEndpointS3: Type: AWS::EC2::VPCEndpoint Properties: ServiceName: com.amazonaws.ap-northeast-1.s3 VpcEndpointType: Gateway VpcId: !Ref VPC RouteTableIds: - !Ref PrivateRouteTable BastionRole: Type: AWS::IAM::Role Properties: Description: EC2 role for SSM AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - ec2.amazonaws.com Action: - sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore BastionInstanceProfile: Type: AWS::IAM::InstanceProfile Properties: Roles: - !Ref BastionRole BastionSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Security group for bastion server GroupName: ssm-bastion-example-bastion-sg VpcId: !Ref VPC BastionServer: Type: AWS::EC2::Instance Properties: ImageId: !Ref BastionImageId InstanceType: t2.micro SubnetId: !Ref PrivateSubnetA SecurityGroupIds: - !Ref BastionSecurityGroup IamInstanceProfile: !Ref BastionInstanceProfile KeyName: !Ref BastionKeyPair Tags: - Key: Name Value: ssm-bastion-example-bastion-server DBSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Security group for bastion exmaple db GroupName: ssm-bastion-example-db-sg VpcId: !Ref VPC SecurityGroupIngress: - SourceSecurityGroupId: !Ref BastionSecurityGroup IpProtocol: tcp FromPort: 3306 ToPort: 3306 DBSubnetGroup: Type: AWS::RDS::DBSubnetGroup Properties: DBSubnetGroupDescription: DB subnet group for bastion example db DBSubnetGroupName: ssm-bastion-example-db-sng SubnetIds: - !Ref PrivateSubnetA - !Ref PrivateSubnetC Tags: - Key: Name Value: ssm-bastion-example-db-sng DB: Type: AWS::RDS::DBInstance Properties: DBInstanceClass: db.t2.micro Engine: MySQL AllocatedStorage: 5 PubliclyAccessible: false DBSubnetGroupName: !Ref DBSubnetGroup VPCSecurityGroups: - !GetAtt DBSecurityGroup.GroupId MasterUsername: !Ref DBMasterUsername MasterUserPassword: !Ref DBMasterPassword Outputs: BastionInstanceId: Description: Bastion server instance id Value: !Ref BastionServer DBEndpoint: Description: Database endpoint Value: !GetAtt DB.Endpoint.Addressスタック作成時に次のパラメータを適切に設定してください。
VPC CIDR
- 作成する VPC の CIDR ブロックです
- 既存の VPC とぶつかる場合は適切な値に変更してください
Private Subnet A CIDR
,Private Subnet C CIDR
- 作成する Private Subnet の CIDR ブロックです
VPC CIDR
を変更した場合はこちらも適切な値に変更してくださいDatabase Master Password
- 作成する RDS インスタンスのマスタパスワードです
Bastion Server Key Pair Name
- 踏み台サーバに接続するためのキーペア名です
- キーペアは予め作成し、秘密鍵をローカル PC にダウンロードしておいてください
どうやって実装するか
SSH Client と AWS CLI の処理を Go で書ければ実現可能なはずです。
SSH Client + AWS CLI の場合の処理の流れは次の通りです。
- SSH Client から ProxyCommand として
aws ssm start-session
を実行する
- AWS CLI は SSM の StartSession を呼んでセッションを開始する
- StartSession のレスポンスとして得られた URL とトークンを使って踏み台インスタンスと WebSocket で通信する
- SSH Client が ProxyCommand の通信を利用して RDS のポートをローカルポートにフォワーディングする
SSH Client の処理は
golang.org/x/crypto/ssh
パッケージを利用することで実装可能です。問題は AWS CLI の処理です。
aws-sdk-go のSSM.StartSession()
で WebSocket 通信用の URL とトークンを得ることができますが、その後の WebSocket 通信の仕様が明らかにされていないのでどう使えばよいのか全くの不明です。AWS CLI の実装を見ると、 Boto3 の
SSM.Client.start_session
で得られた出力を session-manager-plugin に渡していることが分かります。https://github.com/aws/aws-cli/blob/2.1.10/awscli/customizations/sessionmanager.py
どうやら WebSocket 通信はこの session-manager-plugin に任せているようです(session-manager-plugin はバイナリ形式で配布されているため実装の詳細は不明)。
WebSocket 通信の仕様を頑張って解読するのは不毛な上、いつ変更されるかもわからないので今回は AWS CLI 同様に session-manager-plugin を呼び出す形で実装することにします。
AWS CLI と全く同じ呼び出し方をしてあげれば問題なく使えるはずです。
また、AWS CLI と session-manager-plugin 間のインタフェースは互換性を保つためにそう簡単には変更されないものと予想されます。実装
go.modmodule port-forward go 1.15 require ( github.com/aws/aws-sdk-go v1.36.0 github.com/go-sql-driver/mysql v1.5.0 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 )main.gopackage main import ( "database/sql" "encoding/json" "errors" "flag" "fmt" "io" "io/ioutil" "net" "os" "os/exec" "path" "runtime" "strconv" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/ssm" "github.com/go-sql-driver/mysql" "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/knownhosts" ) type config struct { instanceID string region string user string keyPath string localPort uint16 dbHost string dbPort uint16 dbUser string dbPass string } func main() { conf := &config{} var localPort, dbPort uint flags := flag.NewFlagSet("port-forward", flag.ContinueOnError) flags.StringVar(&conf.instanceID, "instance-id", "", "bastion server instance id") flags.StringVar(&conf.region, "region", "ap-northeast-1", "aws region") flags.StringVar(&conf.user, "ssh-user", "ec2-user", "ssh user for bastion server") flags.StringVar(&conf.keyPath, "key", "", "ssh key file path") flags.UintVar(&localPort, "local-port", 9090, "local port for port-fowarding") flags.StringVar(&conf.dbHost, "db-host", "", "database host") flags.UintVar(&dbPort, "db-port", 3306, "database port") flags.StringVar(&conf.dbUser, "db-user", "root", "database user") flags.StringVar(&conf.dbPass, "db-pass", "", "database password") if err := flags.Parse(os.Args[1:]); err != nil { os.Exit(2) } conf.localPort = uint16(localPort) conf.dbPort = uint16(dbPort) if err := run(conf); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } os.Exit(0) } func run(conf *config) error { sess, err := session.NewSession(&aws.Config{ Region: aws.String(conf.region), }) if err != nil { return err } svc := ssm.New(sess) proxyCmd, closeSession, err := openSession(svc, conf.instanceID) if err != nil { return err } defer closeSession() sshConfig, err := newSSHClientConfig(conf.user, conf.keyPath) if err != nil { return err } client, killProxyCmd, err := newSSHClientWithProxyCommand(conf.instanceID, 22, proxyCmd, sshConfig) if err != nil { return err } defer killProxyCmd() defer client.Close() done, err := portForward(conf.localPort, client, conf.dbHost, conf.dbPort) if err != nil { return err } defer done() if err := printDBList("localhost", conf.localPort, conf.dbUser, conf.dbPass); err != nil { return err } return nil } // openSession AWS Systems Manager Session Manager のセッションを開始し、 // session-manager-plugin を実行する *exec.Cmd とセッションを終了する関数を返す。 func openSession(svc *ssm.SSM, instanceID string) (*exec.Cmd, func() error, error) { in := &ssm.StartSessionInput{ DocumentName: aws.String("AWS-StartSSHSession"), Parameters: map[string][]*string{ "portNumber": {aws.String("22")}, }, Target: aws.String(instanceID), } out, err := svc.StartSession(in) if err != nil { return nil, nil, err } close := func() error { in := &ssm.TerminateSessionInput{ SessionId: out.SessionId, } if _, err := svc.TerminateSession(in); err != nil { return err } return nil } cmd, err := sessionManagerPlugin(svc, in, out) if err != nil { defer close() return nil, nil, err } return cmd, close, nil } // sessionManagerPlugin session-manager-plugin を実行する *exec.Cmd を返す。 func sessionManagerPlugin( svc *ssm.SSM, in *ssm.StartSessionInput, out *ssm.StartSessionOutput, ) (*exec.Cmd, error) { command := "session-manager-plugin" if runtime.GOOS == "windows" { command += ".exe" } encodedIn, err := json.Marshal(in) if err != nil { return nil, err } encodedOut, err := json.Marshal(out) if err != nil { return nil, err } region := *svc.Config.Region profile := getAWSProfile() endpoint := svc.Endpoint cmd := exec.Command(command, string(encodedOut), region, "StartSession", profile, string(encodedIn), endpoint) return cmd, nil } // getAWSProfile 有効な AWS Profile を取得する。 func getAWSProfile() string { profile := os.Getenv("AWS_PROFILE") if profile != "" { return profile } enableSharedConfig, _ := strconv.ParseBool(os.Getenv("AWS_SDK_LOAD_CONFIG")) if enableSharedConfig { profile = os.Getenv("AWS_DEFAULT_PROFILE") } return profile } // newSSHClientConfig *ssh.ClientConfig を生成する。 func newSSHClientConfig(user string, keyPath string) (*ssh.ClientConfig, error) { key, err := ioutil.ReadFile(keyPath) if err != nil { return nil, err } signer, err := ssh.ParsePrivateKey(key) if err != nil { return nil, err } hostKeyCallback, err := newHostKeyCallback() if err != nil { return nil, err } return &ssh.ClientConfig{ User: user, Auth: []ssh.AuthMethod{ ssh.PublicKeys(signer), }, HostKeyCallback: hostKeyCallback, }, nil } // newHostKeyCallback ~/.ssh/known_hosts を参照して // ホストの公開鍵を確認する ssh.HostKeyCallback を返す。 func newHostKeyCallback() (ssh.HostKeyCallback, error) { home, err := os.UserHomeDir() if err != nil { return nil, err } knownHosts := path.Join(home, ".ssh", "known_hosts") cb, err := knownhosts.New(knownHosts) if err != nil { return nil, err } return func(hostname string, remote net.Addr, key ssh.PublicKey) error { // net.Pipe() から生成した net.Conn で ssh.Conn を作ると // remote.String() の値が "pipe" となり net.SplitHostPort() が失敗してしまう。 // https://github.com/golang/crypto/blob/5f87f3452ae9/ssh/knownhosts/knownhosts.go#L336 // // hostname には `${instance-id}:22` が入っているので // それを返す net.Addr に差し替えておく。 if remote.String() == "pipe" { remote = &addrImpl{ network: remote.Network(), addr: hostname, } } err := cb(hostname, remote, key) var ke *knownhosts.KeyError if errors.As(err, &ke) { // known_hosts と一致しない場合はエラー if len(ke.Want) > 0 { return ke } f, err := os.OpenFile(knownHosts, os.O_WRONLY|os.O_APPEND, 0644) if err != nil { return err } defer f.Close() // 未知のホストの場合は known_hosts に追記する line := knownhosts.Line([]string{remote.String()}, key) fmt.Fprintln(f, line) return nil } return err }, nil } // addrImple net.Addr の実装。 type addrImpl struct { network string addr string } func (s *addrImpl) Network() string { return s.network } func (s *addrImpl) String() string { return s.addr } // newSSHClientWithProxyCommand ProxyCommand を利用した *ssh.Client を返す。 func newSSHClientWithProxyCommand( host string, port uint16, proxyCmd *exec.Cmd, conf *ssh.ClientConfig, ) (*ssh.Client, func() error, error) { c, s := net.Pipe() proxyCmd.Stdin = s proxyCmd.Stdout = s proxyCmd.Stderr = os.Stderr if err := proxyCmd.Start(); err != nil { return nil, nil, err } done := func() error { return proxyCmd.Process.Kill() } addr := fmt.Sprintf("%s:%d", host, port) conn, chans, reqs, err := ssh.NewClientConn(c, addr, conf) if err != nil { defer done() return nil, nil, err } client := ssh.NewClient(conn, chans, reqs) return client, done, nil } // portForward ポートフォワードを行う。 func portForward( localPort uint16, sshClient *ssh.Client, remoteHost string, remotePort uint16, ) (func(), error) { listener, err := net.Listen("tcp", fmt.Sprintf(":%d", localPort)) if err != nil { return nil, err } remoteAddr := fmt.Sprintf("%s:%d", remoteHost, remotePort) done := make(chan struct{}) go func() { defer listener.Close() for { select { case <-done: return default: } localConn, err := listener.Accept() if err != nil { var ne net.Error if errors.As(err, &ne) && ne.Temporary() { continue } fmt.Fprintln(os.Stderr, "accept failed: ", err) return } remoteConn, err := sshClient.Dial("tcp", remoteAddr) if err != nil { fmt.Fprintln(os.Stderr, "dial failed: ", err) return } go func() { defer localConn.Close() defer remoteConn.Close() if _, err := io.Copy(remoteConn, localConn); err != nil { fmt.Fprintln(os.Stderr, "copy failed: ", err) } }() go func() { if _, err := io.Copy(localConn, remoteConn); err != nil { fmt.Fprintln(os.Stderr, "copy failed: ", err) } }() } }() return func() { close(done) }, nil } // printDBList RDS に接続し DB 一覧を出力する。 func printDBList(host string, port uint16, user, password string) error { conf := mysql.NewConfig() conf.User = user conf.Passwd = password conf.Addr = fmt.Sprintf("%s:%d", host, port) conf.Net = "tcp" dsn := conf.FormatDSN() db, err := sql.Open("mysql", dsn) if err != nil { return err } res, err := db.Query("SHOW DATABASES") if err != nil { return err } defer res.Close() var database string for res.Next() { if err := res.Scan(&database); err != nil { return err } fmt.Println(database) } if err := res.Err(); err != nil { return err } return nil }前述の通り session-manager-plugin がインストールされている必要があります。
次のように踏み台サーバのインスタンス ID ・ RDS インスタンスのエンドポイント・秘密鍵のパスなどを与えて実行します。
(デフォルトでは RDS インスタンスの 3306 ポートがローカルの 9090 ポートにフォワーディングされます)$ go run main.go -instance-id i-xxxxxx -key ~/.ssh/bastion.key -db-host xxxx.xxxx.ap-northeast-1.rds.amazonaws.com -db-pass xxxx information_schema mysql performance_schemaプライベート VPC 内の RDS インスタンスに
SHOW DATABASES
を実行して得られた DB 一覧が出力されます。コードの解説
要点だけを解説します。
メインの処理は
run()
関数に実装されています。func run(conf *config) error { sess, err := session.NewSession(&aws.Config{ Region: aws.String(conf.region), }) if err != nil { return err } svc := ssm.New(sess) proxyCmd, closeSession, err := openSession(svc, conf.instanceID) if err != nil { return err } defer closeSession() sshConfig, err := newSSHClientConfig(conf.user, conf.keyPath) if err != nil { return err } client, killProxyCmd, err := newSSHClientWithProxyCommand(conf.instanceID, 22, proxyCmd, sshConfig) if err != nil { return err } defer killProxyCmd() defer client.Close() done, err := portForward(conf.localPort, client, conf.dbHost, conf.dbPort) if err != nil { return err } defer done() if err := printDBList("localhost", conf.localPort, conf.dbUser, conf.dbPass); err != nil { return err } return nil }次のような流れになっています。
openSession()
で Session Manager のセッションを開始newSSHClientWithProxyCommand()
で session-manager-plugin を ProxyCommand として使う SSH Client を生成portFoward()
で RDS インスタンスのポートをローカルのポートにフォワーディング- ローカルポートに対してクエリを実行
openSession()
// openSession AWS Systems Manager Session Manager のセッションを開始し、 // session-manager-plugin を実行する *exec.Cmd とセッションを終了する関数を返す。 func openSession(svc *ssm.SSM, instanceID string) (*exec.Cmd, func() error, error) { in := &ssm.StartSessionInput{ DocumentName: aws.String("AWS-StartSSHSession"), Parameters: map[string][]*string{ "portNumber": {aws.String("22")}, }, Target: aws.String(instanceID), } out, err := svc.StartSession(in) if err != nil { return nil, nil, err } close := func() error { in := &ssm.TerminateSessionInput{ SessionId: out.SessionId, } if _, err := svc.TerminateSession(in); err != nil { return err } return nil } cmd, err := sessionManagerPlugin(svc, in, out) if err != nil { defer close() return nil, nil, err } return cmd, close, nil }
SSM.StartSession()
を叩いてセッションを開始します。
AWS CLI の実装と、SSH の ProxyCommand 設定での呼び出し方を参考に実装しています。
SSM.StartSession()
の入出力を JSON エンコードしたものを session-manager-plugin に与える必要があるので、ここで session-manager-plugin を実行するための*exec.Cmd
も生成してしまっています(実際に生成している箇所はsessionManagerPlugin()
)。
newSSHClientWithProxyCommand()
// newSSHClientWithProxyCommand ProxyCommand を利用した *ssh.Client を返す。 func newSSHClientWithProxyCommand( host string, port uint16, proxyCmd *exec.Cmd, conf *ssh.ClientConfig, ) (*ssh.Client, func() error, error) { c, s := net.Pipe() proxyCmd.Stdin = s proxyCmd.Stdout = s proxyCmd.Stderr = os.Stderr if err := proxyCmd.Start(); err != nil { return nil, nil, err } done := func() error { return proxyCmd.Process.Kill() } addr := fmt.Sprintf("%s:%d", host, port) conn, chans, reqs, err := ssh.NewClientConn(c, addr, conf) if err != nil { defer done() return nil, nil, err } client := ssh.NewClient(conn, chans, reqs) return client, done, nil }与えられた
*exec.Cmd
を ProxyCommand として使用する SSH Client を生成します。
net.Pipe()
を使用してコマンドの入出力を SSH Client に結び付けるのがポイントです。
portFoward()
// portForward ポートフォワードを行う。 func portForward( localPort uint16, sshClient *ssh.Client, remoteHost string, remotePort uint16, ) (func(), error) { listener, err := net.Listen("tcp", fmt.Sprintf(":%d", localPort)) if err != nil { return nil, err } remoteAddr := fmt.Sprintf("%s:%d", remoteHost, remotePort) done := make(chan struct{}) go func() { defer listener.Close() for { select { case <-done: return default: } localConn, err := listener.Accept() if err != nil { var ne net.Error if errors.As(err, &ne) && ne.Temporary() { continue } fmt.Fprintln(os.Stderr, "accept failed: ", err) return } remoteConn, err := sshClient.Dial("tcp", remoteAddr) if err != nil { fmt.Fprintln(os.Stderr, "dial failed: ", err) return } go func() { defer localConn.Close() defer remoteConn.Close() if _, err := io.Copy(remoteConn, localConn); err != nil { fmt.Fprintln(os.Stderr, "copy failed: ", err) } }() go func() { if _, err := io.Copy(localConn, remoteConn); err != nil { fmt.Fprintln(os.Stderr, "copy failed: ", err) } }() } }() return func() { close(done) }, nil }ポートフォワードを行います。
Listener.Accept()
で得られたローカルポートのnet.Conn
と SSH Client から RDS インスタンスにDial()
して得られたnet.Conn
とを goroutine 内で相互にio.Copy()
することでポートフォワードを実現できます。無限ループ内で
Accept()
,Dial()
することで複数のコネクションを扱うことが可能です。参考
- 投稿日:2020-11-18T17:03:44+09:00
UbuntuにAWS IoT Greengrassをインストールする
はじめに
会社の業務でAWS IoT Greengrassについて勉強したので、今回から数回に分けてUbuntu搭載デバイスとAWS IoTを利用したアプリケーションを紹介していきたいと思います。
初回はクラウド上でのGreengrass Groupの作成と、エッジデバイスへのGreengrass Coreのインストール方法を紹介します。Azure IoT Edgeを使った記事もあるので興味のある方は是非ご覧ください。
環境
動作確認済デバイス(OS)
e-RT3 Plus F3RP70-2L1(Ubuntu 18.04 32bit)
横河電機のエッジコントローラです。AWS IoT Greengrassの認定デバイス2に登録されています(e-RT3のページはこちら)。Raspberry Pi 4 Model B (Ubuntu Server 20.04 32bit)
これらのデバイスでは armhf アーキテクチャのパッケージが動作します。
また、Windows 10 搭載のPCでデバイスを操作しています。AWS IoT Greengrassとは
AWS IoT GreengrassとはAWSが提供しているエッジコンピューティングのためのソフトウェアです。
Greengrassをエッジデバイスにインストールすることにより、クラウドとの接続やクラウドからのアプリケーションのデプロイなどを容易に行うことができます。
また、クラウドの一部の機能をエッジデバイスに拡張することにより、データソースに近い場所でのデータ収集や分析、ローカルイベントに対するアクション、ローカルデバイス同士の通信などを行うことができます。
詳しくはAWS IoT Greengrassの公式サイトをご覧ください。準備
AWSアカウントの作成
AWSのアカウントを所持していない場合は作成します。
https://aws.amazon.com/jp/
制限付きの無料利用枠もあります。
今回使用するAWS IoTの無料利用枠はここで確認できます。WinSCPのインストール
PCからデバイスにファイルを転送するために、PCにWinSCPをインストールします。
インストール方法や使い方については公式サイトをご覧ください。
https://winscp.net/eng/index.phpPython3.8のインストール
※この設定はe-RT3を使用している場合のみ必要です。
※e-RT3の場合、一般ユーザーでsudoコマンドを実行するにはsudoers設定が必要です。GreengrassのLambdaで使用するPython3.8をインストールします。
sudo apt update sudo apt install python3.8インストールの成功を確認します。
username@ubuntu:~$ python3.8 --version Python 3.8.0Java8のインストール
Greengrassのストリームマネージャーで使用するJava8をインストールします。
sudo apt update sudo apt install openjdk-8-jdkインストールしたJavaにリンクを張ります。
sudo ln /etc/alternatives/java /usr/bin/java8
インストールの成功を確認します。
username@ubuntu:~$ java8 -version openjdk version "1.8.0_275" OpenJDK Runtime Environment (build 1.8.0_275-8u275-b01-0ubuntu1~18.04-b01) OpenJDK Client VM (build 25.275-b01, mixed mode)cgroupの有効化
※この設定はRaspberry Piを使用している場合のみ必要です。
コンテナでLambdaを実行するためにcgroupを有効にします。
/boot/firmware/cmdline.txt
を開きます。sudo vi /boot/firmware/cmdline.txt
既存の行の末尾に以下の値を追加します。
cgroup_enable=memory cgroup_memory=1デバイスを再起動します。
sudo reboot
Greengrass Groupの作成
AWSの公式ガイドの手順3に従って、AWSマネジメントコンソールでGreengrass Groupを作成します。
8.「これらのリソースはtar.gzとしてダウンロードしてください」をクリックして、デバイスをクラウドに接続するためのセキュリティリソースをダウンロードします。
このファイルは後からダウンロードすることができないので、必ずここでダウンロードしておきましょう。
ダウンロードできたら「完了」をクリックして設定を終了します。
Greengrass Coreのデバイスへのインストール
デバイスにGreengrass Coreソフトウェアをインストールして起動します。
Greengrass Coreソフトウェアのインストール方法は複数ありますが、ここではAPTリポジトリからインストールします4。
※APTリポジトリからのインストールではOTA更新がサポートされていません。OTA更新を利用したい方は他の方法でインストールしてください。セキュリティリソースとルートCA証明書のセットアップ
デバイスをクラウドに接続するために必要なセキュリティリソースとルートCA証明書をセットアップします。
セキュリティリソースのインストール
WinSCPを起動してデバイスと接続し、ユーザーのホームディレクトリにGreengrass Groupの作成の手順8でダウンロードしたセキュリティリソース(tar.gzファイル)を置きます。
以下のコマンドを実行してセキュリティリソースをインストールします。
<hash>
の部分はセキュリティリソースのファイル名に合わせて置き換えてください。sudo mkdir -p /greengrass sudo tar -xzvf ~/<hash>-setup.tar.gz -C /greengrassルートCA証明書のダウンロード
/greengrass/certs
へ移動します。cd /greengrass/certs/
root.ca.pem
という名前でAmazon Root CA 1証明書をダウンロードします。sudo wget -O root.ca.pem https://www.amazontrust.com/repository/AmazonRootCA1.pemダウンロードした
root.ca.pem
が空でない(正しくダウンロードされた)ことを確認します。ファイルが空の場合はもう一度ダウンロードを試してみてください。cat root.ca.pem
Greengrass Coreソフトウェアのインストール
Greengrassのシステムアカウントを作成します。
sudo adduser --system ggc_user sudo addgroup --system ggc_groupAWS IoT Greengrass キーリングパッケージをインストールして、リポジトリを追加します。
cd ~ sudo wget -O aws-iot-greengrass-keyring.deb https://d1onfpft10uf5o.cloudfront.net/greengrass-apt/downloads/aws-iot-greengrass-keyring.deb sudo dpkg -i aws-iot-greengrass-keyring.debusername@ubuntu:~$ echo "deb https://dnw9lb6lzp2d8.cloudfront.net stable main" | sudo tee /etc/apt/sources.list.d/greengrass.list deb https://dnw9lb6lzp2d8.cloudfront.net stable mainパッケージのリストを更新し、Greengrass Coreソフトウェアをインストールします。
sudo apt update sudo apt install aws-iot-greengrass-coreGreengrassデーモンを開始します。
sudo systemctl start greengrass.service
以下のコマンドを実行し、表示された
Active
の状態がactive(running)
であればデーモンは正常に動作しています。username@ubuntu:~$ systemctl status greengrass.service * greengrass.service - Greengrass Daemon Loaded: loaded (/lib/systemd/system/greengrass.service; disabled; vendor preset: enabled) Active: active (running) since Fri 2020-11-06 06:31:07 UTC; 14min ago Process: 2159 ExecStart=/greengrass/ggc/core/greengrassd start (code=exited, status=0/SUCCESS) Main PID: 2163 (5) Tasks: 10 (limit: 2366) CGroup: /system.slice/greengrass.serviceデバイス起動時に自動的に起動するには以下のコマンドを実行してください。
sudo systemctl enable greengrass.service※デバイスがproxy環境下にある場合は追加でproxy設定が必要になります。
動作確認
空のデプロイを行ってデバイスがクラウドと接続できるか確認します。
まとめ
クラウド上でのGreengrass Groupの作成と、デバイスへのGreengrass Coreのインストールを行いました。
次回はLambdaの作成とデプロイを行う予定ですのでお楽しみに!補足
proxy設定
デバイスがproxy環境下にある場合はproxy設定が必要になります。
環境により設定は異なりますが、参考までに今回私が行った設定を紹介します。環境変数の設定
こちらの記事をご覧ください。
Greengrass Coreの設定
AWSの公式ドキュメント5に従ってGreengrass Coreの設定を行います。
Greengrassが起動中の場合は停止します。
sudo systemctl stop greengrass.service
/greengrass/config/config.json
に書き込み権限を追加して開きます。sudo chmod +w /greengrass/config/config.json sudo vi /greengrass/config/config.jsoncoreThingオブジェクト内にiotMqttPortオブジェクトとnetworkProxyオブジェクトを追加します。
追加した後のファイルは以下のようになります。/greengrass/config/config.json{ "coreThing" : { "caPath" : "root.ca.pem", "certPath" : "3283c6f04d.cert.pem", "keyPath" : "3283c6f04d.private.key", "thingArn" : "arn:aws:iot:ap-northeast-1:xxxxxxxxxxxx:thing/eRT3Group_Core", "iotHost" : "xxxxxxxxxxxx-ats.iot.ap-northeast-1.amazonaws.com", "iotMqttPort" : 443, "ggHost" : "greengrass-ats.iot.ap-northeast-1.amazonaws.com", "keepAlive" : 600, "networkProxy":{ "proxy":{ "url" : "http://username:password@example.com:port/" } } }, ...ファイルから書き込み権限を削除し、Greengrassを再度起動します。
sudo chmod -w /greengrass/config/config.json sudo systemctl start greengrass.service参考
- 投稿日:2020-11-18T16:59:47+09:00
AWSサポートプラン4種比較
具体的なサポート内容は 公式情報を参照
ベーシック 開発者 ビジネス エンタープライズ 対応期間 - 月~金,09:00~18:00 24時間年中無休 24時間年中無休 応答速度 - 12h以内 1h以内 15min以内 コスト 無料 29USD~ 100USD~ 15000USD~
- 投稿日:2020-11-18T16:07:20+09:00
個人的によく使うAWS CLIコマンド
概要
個人的によく使うAWS CLIのコマンド集.
yamlが好きなのでjson2yaml
が頻出しますが,そこは従うなり無視するなりご自由に...EC2インスタンス情報のサマリ
INSTANCE_ID='' AWS_REGION='ap-northeast-1' aws ec2 describe-instances \ --filters Name=instance-id,Values=${INSTANCE_ID} \ --region ${AWS_REGION} \ --query 'Reservations[].Instances[].{InstanceId:InstanceId,PrivateIpAddress:PrivateIpAddress,PrivateDnsName:PrivateDnsName,PublicIpAddress:PublicIpAddress,PublicDnsName:PublicDnsName,Tags:Tags[]}[0]' \ --output json \ | json2yamlOutput
Tags: - Value: ****** Key: DeviceID - Value: ****** Key: Name - Value: ****** Key: PIC - Value: ****** Key: ExpireDate - Value: ****** Key: CreatedDate InstanceId: ****** PublicDnsName: ****** PrivateDnsName: ****** PublicIpAddress: ****** PrivateIpAddress: ******インターネットゲートウェイ情報
INTERNET_GATEWAY_IDS='' aws ec2 describe-internet-gateways \ --internet-gateway-ids ${INTERNET_GATEWAY_IDS} \ | json2yamlOutput
InternetGateways: - OwnerId: ****** Tags: - Value: ****** Key: Name - Value: ****** Key: CreatedDate - Value: ****** Key: ExpireDate Attachments: - State: available VpcId: ****** InternetGatewayId: igw-******ルートテーブル情報
ROUTE_TABLE_IDS='rtb-******' aws ec2 describe-route-tables \ --route-table-ids ${ROUTE_TABLE_IDS} \ | json2yamlOutput
RouteTables: - Associations: - AssociationState: State: associated RouteTableAssociationId: ****** Main: true RouteTableId: rtb-****** RouteTableId: rtb-****** VpcId: vpc-****** PropagatingVgws: - GatewayId: vgw-****** Tags: - Value: ****** Key: ExpireDate - Value: ****** Key: Name - Value: ****** Key: CreatedDate Routes: - GatewayId: local DestinationCidrBlock: ******/** State: active Origin: CreateRouteTable - GatewayId: vgw-****** DestinationCidrBlock: ******/** State: active Origin: EnableVgwRoutePropagation OwnerId: ******AMI情報
IMAGE_IDS='' AWS_REGION='ap-northeast-1' aws ec2 describe-images \ --region ${AWS_REGION} \ --image-ids ${IMAGE_IDS} \ | json2yamlOutput
Images: - ProductCodes: - ProductCodeId: ****** ProductCodeType: ****** Description: ****** Tags: - Value: ****** Key: Name VirtualizationType: hvm Hypervisor: xen EnaSupport: true SriovNetSupport: simple ImageId: ****** State: available BlockDeviceMappings: - DeviceName: /dev/sda1 Ebs: SnapshotId: ****** DeleteOnTermination: false VolumeType: gp2 VolumeSize: 8 Encrypted: true Architecture: x86_64 ImageLocation: ******/****** RootDeviceType: ebs OwnerId: ****** RootDeviceName: /dev/sda1 CreationDate: '2020-01-11T00:00:00.000Z' Public: false ImageType: machine Name: ******セキュリティグループ情報
GROUP_IDS='sg-****** sg-******'
のように複数指定可能.GROUP_IDS='' aws ec2 describe-security-groups \ --group-ids ${GROUP_IDS} \ | json2yamlOutput
SecurityGroups: - IpPermissionsEgress: - IpProtocol: '-1' PrefixListIds: [] IpRanges: - CidrIp: 0.0.0.0/0 UserIdGroupPairs: [] Ipv6Ranges: [] Description: test-sg-1 IpPermissions: - PrefixListIds: [] FromPort: 80 IpRanges: - CidrIp: ***.***.***.***/32 ToPort: 80 IpProtocol: tcp UserIdGroupPairs: [] Ipv6Ranges: [] GroupName: test-sg-1 VpcId: ****** OwnerId: ****** GroupId: ****** - IpPermissionsEgress: - IpProtocol: '-1' PrefixListIds: [] IpRanges: - CidrIp: 0.0.0.0/0 UserIdGroupPairs: [] Ipv6Ranges: [] Description: test-sg-2 IpPermissions: - PrefixListIds: [] FromPort: 80 IpRanges: - CidrIp: ***.***.***.***/32 ToPort: 80 IpProtocol: tcp UserIdGroupPairs: [] Ipv6Ranges: [] GroupName: test-sg-2 VpcId: ****** OwnerId: ****** GroupId: ******S3バケット一覧
aws s3api list-buckets \ --query "Buckets[].Name"Output
[ "test-bucket-1", "test-bucket-2", "test-bucket-3", "test-bucket-4", "test-bucket-5" ]S3バケットのタグ情報
BUCKET_NAME='' aws s3api get-bucket-tagging \ --bucket ${BUCKET_NAME} \ | json2yamlOutput
TagSet: - Value: '20210101' Key: Expire - Value: '20200101' Key: Created - Value: Production Key: Environment - Value: test-bucket-1 Key: NameS3 ls
便利(?)なオプション.結果についてはOutput参照
--human
- ファイルサイズに単位を付けて表示
--sum
- オブジェクト数,ファイルサイズ合計を表示
--recursive
- 再帰的表示
aws s3 ls s3://test-bucket-1/ \ --recursive \ --human \ --sumOutput
aws s3 ls s3://test-bucket-1/ \ --recursive 2010-01-11 18:16:16 88 AWSLogs/AccessLog 2010-01-11 18:20:08 646 AWSLogs/elasticloadbalancing/.../test-web.log.gzaws s3 ls s3://test-bucket-1/ \ --recursive \ --sum 2010-01-11 18:16:16 88 AWSLogs/AccessLog 2010-01-11 18:20:08 646 AWSLogs/elasticloadbalancing/.../test-web.log.gz Total Objects: 2 Total Size: 734aws s3 ls s3://test-bucket-1/ \ --recursive \ --human \ --sum 2010-01-11 18:16:16 88 Bytes AWSLogs/AccessLog 2010-01-11 18:20:08 646 Bytes AWSLogs/elasticloadbalancing/.../test-web.log.gz Total Objects: 2 Total Size: 734 Bytes備考
随時加筆予定...
- 投稿日:2020-11-18T15:16:20+09:00
Boto3でAWSのリソースを取得する時に作成したリソースだけ表示する
前提
AWSのリソースをBoto3で取得する時に、一部のリソースだと作った覚えのないリソースが含まれてしまうことがある。
これらはおそらくAWSが自動で作成したり、デフォルトで用意されているものだろう。
私の場合、スナップショット一覧を取得しようとしたら、AWSコンソール上では見たことないし、作った覚えがないリソースがあった。
以下がソースである。client = self.session.client('ec2') snapshots = client.describe_snapshots()対処方法
自身が作成したものだけを表示するためには
OwnerIds
を指定してあげれば良い。自分が作ったリソースを表示
client = self.session.client('ec2') snapshots = client.describe_snapshots(OwnerIds=['self'])ユーザIDを指定
client = self.session.client('ec2') snapshots = client.describe_snapshots(OwnerIds=['xxxxxxxxxxxx'])※ xxxxxxxxxxxx にユーザIDを指定
- 投稿日:2020-11-18T15:02:45+09:00
[AWS SAM] Lambda Layerの定義(nodejs)
目的
前回記事でcfn-response モジュールを使用したが、
インライン実装にしか対応しておらず不便なためLayerとして実装するファイル構成
lambda-layer/ ├─ layers/ │ └─ cfn-response-layer/ │ └─ nodejs/ │ ├─ node_modules/ # [npm i cfn-response] で生成 │ ├─ cfn-response/ │ │ ├─ package.json # [npm init] で生成 │ │ └─ index.js # Layerとして提供する機能 │ │ │ ├─ package.json │ └─ package-lock.json # [npm i cfn-response] で生成 │ └─ template.yaml # SAMテンプレート※
nodejs
フォルダは名前変更不可
※index.js
ファイルは名前変更不可ではないが、別名とした場合はnpm init
した際にファイル名の指定が必要SAMテンプレート
template.yamlCfnResponse: Type: AWS::Serverless::LayerVersion Properties: LayerName: !Sub ${RootStackName}_cfn-response ContentUri: layers/cfn-response-layer CompatibleRuntimes: - nodejs12.x RetentionPolicy: DeleteLayerとして提供する機能は
nodejs
フォルダに入れることが決められているので、
ContentUri
には、その1個手前までのパスを指定cfn-response モジュール実装
index.js
index.js
にcfn-response モジュールの機能を実装する
githubで公開されているcfn-response モジュールのコードを基本コピペする
SUCCESS
,FAILED
の定義値と、send関数を提供するindex.jsconst https = require('https'); const url = require('url'); module.exports = { SUCCESS: 'SUCCESS', FAILED: 'FAILED', send( event, context, responseStatus, responseData, physicalResourceId, noEcho, ) { return new Promise((resolve, reject) => { const responseBody = JSON.stringify({ Status: responseStatus, Reason: `See the details in CloudWatch Log Stream: ${context.logStreamName}`, PhysicalResourceId: physicalResourceId || context.logStreamName, StackId: event.StackId, RequestId: event.RequestId, LogicalResourceId: event.LogicalResourceId, NoEcho: noEcho || false, Data: responseData, }); console.log('Response body:\n', responseBody); const parsedUrl = url.parse(event.ResponseURL); const options = { hostname: parsedUrl.hostname, port: 443, path: parsedUrl.path, method: 'PUT', headers: { 'content-type': '', 'content-length': responseBody.length, }, }; const request = https.request(options, response => { console.log(`Status code: ${response.statusCode}`); console.log(`Status message: ${response.statusMessage}`); resolve(context.done()); }); request.on('error', error => { console.log(`send(..) failed executing https.request(..): ${error}`); reject(context.done(error)); }); request.write(responseBody); request.end(); }); }, };
nodejs/cfn-response/package.json
nodejs/cfn-response/
以下でnpm init
を実行する
全てデフォルト値で回答すると以下のようなpackage.json
が生成されるnodejs/cfn-response/package.json{ "name": "cfn-response", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC" }
nodejs/package.json
package.json
は内容{}
のみで作成
npm install <パッケージ名>
を実行すると追記される
node_modules
およびpackage-lock.json
nodejs
フォルダ以下でnpm i cfn-response
を実行すると生成される
index.js
内でhttps
、url
パッケージを使用しているが、これらはnodejsの標準モジュールのためインストールは不要Layerの使用
SAMテンプレート
ApplyNotificationFunction: Type: AWS::Serverless::Function Properties: FunctionName: !Sub ${RootStackName}-ApplyNotificationFunction CodeUri: handlers Role: !GetAtt 'ApplyNotificationFunctionRole.Arn' Layers: - !Ref CfnResponse # 先ほどSAMテンプレートで定義したLayerを参照コード
const cfnResponse = require('cfn-response'); exports.handler = async (event, context) => { try { if (event.RequestType !== 'Create') { await cfnResponse.send(event, context, cfnResponse.SUCCESS); return; } ...
- 投稿日:2020-11-18T10:56:47+09:00
CloudFormationでEC2を構築した時にEBSにTagsを付与できない
かゆいところに手が届かない。
BlockDeviceMappings: - DeviceName: /dev/sda1 Ebs: VolumeSize: 50 Tags # 不可能 - Key: "keyname1" Value: "value1"公式が情報をナレッジセンターに出しているが、余計な情報が多すぎる、ここだけでいい。
UserData: Fn::Base64: !Sub | #!/bin/bash AWS_AVAIL_ZONE=$(curl http://169.254.169.254/latest/meta-data/placement/availability-zone) AWS_REGION="`echo \"$AWS_AVAIL_ZONE\" | sed 's/[a-z]$//'`" AWS_INSTANCE_ID=$(curl http://169.254.169.254/latest/meta-data/instance-id) ROOT_VOLUME_IDS=$(aws ec2 describe-instances --region $AWS_REGION --instance-id $AWS_INSTANCE_ID --output text --query Reservations[0].Instances[0].BlockDeviceMappings[0].Ebs.VolumeId) aws ec2 create-tags --resources $ROOT_VOLUME_IDS --region $AWS_REGION --tags Key=Name,Value={つけたい名前}
aws ec2 create-tags
するためにはIAMロールとポリシーが必要だから、IAMを書いているとここまでする必要あるのか?ともやもやする。
ちなみに新規にVolumeを作るときは普通に対応している。Type: AWS::EC2::Volume Properties: AutoEnableIO: Boolean AvailabilityZone: String Encrypted: Boolean Iops: Integer KmsKeyId: String Size: Integer SnapshotId: String Tags: - Tag # だよね~ VolumeType: StringCloudFormationで必要なだけインスタンス立ててから、LambdaでEC2のTagから拾ってきて一括付与でもいいんだけど、めんどくさ過ぎるからそもそも対応してほしい。
- 投稿日:2020-11-18T10:54:32+09:00
CloudFormationに日本語コメントを含めるとエラーになる場合の解決方法
前提条件
- Windowsで*.ymlに日本語コメントを含めてAWS CLIを叩いた場合のみ起こる
- マネコンから*.ymlをアップロードした場合は不明
- AWS CLIをインストーラからインストールしている場合のみ起こる
- 結論から言うとインストーラからインストールしているAWS CLIをアンインストールしてpipでgithubからインストールすればいい
テンプレートに日本語コメントを含めるとエラーになる
# これは日本語だ AWSTemplateFormatVersion: '2010-09-09' Parameters: Parameter: Type: Number Resources: # 日本語だ、これは Lambda: Type: 'AWS::Lambda::Function'こういうテンプレートファイルを
aws cloudformation create-stack --template-body file://hoge.yml --stack-name foo
と実行するとError parsing parameter '--template-body': Unable to load paramfile (hoge.yml), text contents could not be decoded. If this is a binary file, please use the fileb:// prefix instead of the file:// prefix.いったエラーが出ることがある。fileb://とあるが、もちろんテンプレートはバイナリではない。AWS CLI実行時に
--debug
をつけるとこのようなエラーが見える。UnicodeDecodeError: 'cp932' codec can't decode byte 0xef in position 89: illegal multibyte sequence文字コード関連のエラーらしい
VS Codeで記述したので、テンプレートファイルはUTF8である。根本的解決にならないが、テンプレートファイルをShift_JISに変換してAWS CLIを実行すれば問題ない。ただ、JISのままだと、GithubにPushすると文字化けする等、いろいろと面倒。というかつい先日にMicrosoftからUnicodeを使ってくれとお触れが出たばっかりである。
AWS CLIが食っている文字コードをUTF8にする
調べたところ、AWS CLIは後ろでboto(Python)が動いているらしい。
C:\> aws --version aws-cli/2.0.3 Python/3.7.5 Windows/10 botocore/2.0.0dev7管理者権限を持つCMDなりPowershellで環境変数を付加してやる。参考にしたのはこの記事。
setx /m PYTHONUTF8 1何も変わらん( ^ω^)…
'cp932' codec can't decode byte 0xef in position 89: illegal multibyte sequenceCLIが使っているPythonは環境変数を読んでないらしい
インストーラから入れたAWS CLIをアンインストールする。
参考にした記事はこの記事、要するにPythonが環境変数を読んでないなら、環境変数を読ませたいPythonのパッケージマネージャであるpipから、botoとAWS CLIをインストールしてやればいい。pip install https://github.com/boto/botocore/archive/v2.tar.gz pip install https://github.com/aws/aws-cli/archive/v2.tar.gz解決した
aws cloudformation create-stack --template-body file://lambda.yml --stack-name foo { "StackId": "arn:aws:cloudformation:ap-northeast-1:111111111111:stack/foo/00000000-1111-1111-1111-111111111111" }環境構築でハマるのもストレスマッハだけど、文字コード関連もそれに準ずるくらいイラつく。
参考記事
WindowsでCP932(Shift-JIS)エンコード以外のファイルを開くのに苦労した話
https://qiita.com/Yuu94/items/9ffdfcb2c26d6b33792e
Windows 上の Python で UTF-8 をデフォルトにする
https://qiita.com/methane/items/9a19ddf615089b071e71
AWS CLI v2をpipからインストールしてみた
https://dev.classmethod.jp/cloud/aws/install-aws-cli-v2-from-sourcecode/
- 投稿日:2020-11-18T10:54:32+09:00
CloudFormationに日本語コメントを含みたい
前提条件
- Windowsで*.ymlに日本語コメントを含めてAWS CLIを叩いた場合のみ起こる
- マネコンから*.ymlをアップロードした場合は不明
- AWS CLIをインストーラからインストールしている場合のみ起こる
- 結論から言うとインストーラからインストールしているAWS CLIをアンインストールしてpipでgithubからインストールすればいい
テンプレートに日本語コメントを含めるとエラーになる
# これは日本語だ AWSTemplateFormatVersion: '2010-09-09' Parameters: Parameter: Type: Number Resources: # 日本語だ、これは Lambda: Type: 'AWS::Lambda::Function'こういうテンプレートファイルを
aws cloudformation create-stack --template-body file://hoge.yml --stack-name foo
と実行するとError parsing parameter '--template-body': Unable to load paramfile (hoge.yml), text contents could not be decoded. If this is a binary file, please use the fileb:// prefix instead of the file:// prefix.いったエラーが出ることがある。fileb://とあるが、もちろんテンプレートはバイナリではない。AWS CLI実行時に
--debug
をつけるとこのようなエラーが見える。UnicodeDecodeError: 'cp932' codec can't decode byte 0xef in position 89: illegal multibyte sequence文字コード関連のエラーらしい
VS Codeで記述したので、テンプレートファイルはUTF8である。根本的解決にならないが、テンプレートファイルをShift_JISに変換してAWS CLIを実行すれば問題ない。ただ、JISのままだと、GithubにPushすると文字化けする等、いろいろと面倒。というかつい先日にMicrosoftからUnicodeを使ってくれとお触れが出たばっかりである。
AWS CLIが食っている文字コードをUTF8にする
調べたところ、AWS CLIは後ろでboto(Python)が動いているらしい。
C:\> aws --version aws-cli/2.0.3 Python/3.7.5 Windows/10 botocore/2.0.0dev7管理者権限を持つCMDなりPowershellで環境変数を付加してやる。参考にしたのはこの記事。
setx /m PYTHONUTF8 1何も変わらん( ^ω^)…
'cp932' codec can't decode byte 0xef in position 89: illegal multibyte sequenceCLIが使っているPythonは環境変数を読んでないらしい
インストーラから入れたAWS CLIをアンインストールする。
参考にした記事はこの記事、要するにPythonが環境変数を読んでないなら、環境変数を読ませたいPythonのパッケージマネージャであるpipから、botoとAWS CLIをインストールしてやればいい。pip install https://github.com/boto/botocore/archive/v2.tar.gz pip install https://github.com/aws/aws-cli/archive/v2.tar.gz解決した
aws cloudformation create-stack --template-body file://lambda.yml --stack-name foo { "StackId": "arn:aws:cloudformation:ap-northeast-1:111111111111:stack/foo/00000000-1111-1111-1111-111111111111" }環境構築でハマるのもストレスマッハだけど、文字コード関連もそれに準ずるくらいイラつく。
参考記事
WindowsでCP932(Shift-JIS)エンコード以外のファイルを開くのに苦労した話
https://qiita.com/Yuu94/items/9ffdfcb2c26d6b33792e
Windows 上の Python で UTF-8 をデフォルトにする
https://qiita.com/methane/items/9a19ddf615089b071e71
AWS CLI v2をpipからインストールしてみた
https://dev.classmethod.jp/cloud/aws/install-aws-cli-v2-from-sourcecode/
- 投稿日:2020-11-18T08:24:31+09:00
Amazon SageMakerとAzure MLにおける機械学習モデルのサービング技術比較(前編)
初版: 2020年11月18日
著者: 橋本恭佑、柿田将幸, 株式会社 日立製作所はじめに
機械学習技術のビジネスへの活用ニーズが高まり、機械学習モデルの開発や運用のライフサイクルを支援するフレームワークに注目が集まっています。
パブリッククラウドでは上述のフレームワークを公開しており、フレームワーク利用者は機械学習モデルを迅速に作成し自身のシステムに組み込んで公開することができます。
機械学習モデルをシステムに組み込み、サービスとして公開する一連の技術はサービング技術と呼ばれており、
システム構築と運用を担うSEにとって重要な技術です。
本連載ではAI案件に対応するSEを対象として、Amazon SageMakerとAzure MLを例に、パブリッククラウドが提供するフレームワークとサービング技術を概観し、2つのクラウドベンダにどのような違いや特徴があるかを説明します。
なお、本連載に記載のAmazon SageMakerまたはAzure MLの情報は2020年9月末日現在のものであり、今後のアップデート等によって内容が変わることがあります。投稿一覧
- Amazon SageMakerとAzure MLにおける機械学習モデルのサービング技術比較(前編)・・・本投稿
- Amazon SageMakerとAzure MLにおける機械学習モデルのサービング技術比較(後編)
機械学習システムのライフサイクルとサービング技術
機械学習案件ではデータサイエンティストとSEが連携して運用サイクルを回します。
パブリッククラウドベンダの機械学習サービスでは、図1のように機械学習の継続的な運用サイクルを実現する一連の機能群を提供しています。
本連載ではAmazon SageMakerとAzure MLの推論システム構築機能の違いを解説します。出典: Machine Learning with Amazon SageMaker(https://docs.aws.amazon.com/sagemaker/latest/dg/how-it-works-mlconcepts.html)
パブリッククラウドで実現する推論システムの種別
推論システムは利用用途により図2のようにストリーム型とバッチ型の2種類に大別されます。
特にストリーム型はリクエストの到着間隔がランダムであり、バッチ型と比較して
事前の性能設計が困難であるため、必要に応じた計算リソースの増減が可能なパブリッククラウドにより実現しやすいといえます。
そこで本連載では、ストリーム型について紹介します。ストリーム型推論システムの概要と構築の勘所
パブリッククラウドにおけるサービングでは、ゲートウェイ、前処理、モデル、後処理、およびそれらの実行に必要なライブラリ群を、まとめて「ランタイム」としてデプロイします。
以後では特に初学者向けに、パブリッククラウド上でモデルを学習させてランタイムを作成する場合の構築の勘所を、アプリ層と基盤層に分けて説明します。ストリーム型推論システム構築に向けたパブリッククラウドサービスの機能比較: アプリ層
表1から、Amazon SageMakerとAzure MLの両者で、前処理や後処理の実装方法が大きく異なることがわかります。
モデル作成時は、各サービスであらかじめ用意されたバージョンのOSSを利用します。表1: パブリッククラウドサービスの機能比較: アプリ層
カテゴリ 項目 Amazon SageMaker Azure ML ゲートウェイ 送信データの形式 MIME Type(テキスト、画像、音声) MIME Type(テキスト、画像、音声) 受信データの形式 MIME Type(テキスト、画像、音声) MIME Type(テキスト、画像、音声) 通信プロトコル HTTPのみ HTTPのみ 認証 IAMベースのアカウント認証 Azureロールベースのアカウント認証 前処理・後処理 実装方法(後編で実機検証) 前処理と後処理を異なる関数に分けて実装 前処理・後処理も同じ関数内に実装 モデル 学習や推論に利用するOSS コンテナイメージに含まれるOSSを利用する コンテナイメージに含まれるOSSを利用する モデルDB S3 Azure DB ストリーム型推論システム構築に向けたパブリッククラウドサービスの機能比較: 基盤層(ランタイム)
パブリッククラウドで本番環境を構築する場合は、ランタイムをコンテナで作成します。
デフォルトまたは自作のコンテナイメージを選択して、コンテナ作成時に必要なOSSを導入・更新します。
Azure MLではランタイム作成時にpipやcondaを利用して独自OSSを追加することもできます。表2: パブリッククラウドサービスの機能比較: 基盤層(ランタイム)
項目 Amazon SageMaker Azure ML ランタイムの種類 コンテナ コンテナ デフォルトで提供されるコンテナイメージに含まれるOSSの種類 TensorFlow, PyTorch, Apache MXNet, Chainer, Keras, Gluon, Horovod, scikit-learn, および Deep Graph Library TensorFlow, PyTorch, Keras, scikit-learn, ONNX 上述のOSSの保守期限 言及なし(サポートページ参照) 言及なし(サポートページ参照) ランタイム作成時のOSS追加方法(後編で実機検証) 事前にコンテナイメージを用意する 事前にコンテナイメージを用意する、またはランタイム作成時にnotebook経由でpipやcondaを利用して任意のOSSを追加する ランタイムに含まれるOSSのアップデート可否 ×(新規ランタイム作成要) ×(新規ランタイム作成要) ストリーム型推論システム構築に向けたパブリッククラウドサービスの機能比較: 基盤層(実行基盤)
Amazon SageMakerではAWSのマネージド環境にランタイムをデプロイしますが、Azure MLではランタイムの実行環境がコンテナ基盤(AKS)に限定されます。
Azure MLの基盤層はコンテナ基盤(AKS)の制約を受けるため、運用時はAKSの知識が必要といえます。
また、Amazon SageMakerとAzure MLの両方ともに可用性や運用・保守の機能についてはコンテナ基盤の機能に準拠していることがわかります。表3: パブリッククラウドサービスの機能比較: 基盤層(実行基盤)
項目 Amazon SageMaker Azure ML ランタイム実行環境(後編で実機検証) マネージド環境が提供される 別途コンテナ基盤(Azure Kubernetes Service)の用意が必要 コンテナの性能 コンテナホストに依存 コンテナホストに依存 コンテナホストの性能 2cpu/4GB ~ 96cpu/384GB 2cpu/4GB ~ 64cpu/256GB コンテナ基盤(kubernetesクラスタ)のサイズ -(SageMakerの裏は意識しない) ノード3つ以上、クラスタ全体で12コア以上 複数ランタイムの並列処理 〇 × 1ランタイム内の並列処理 〇 × GPUの利用 〇 〇 オートスケール 〇 △(AKSで可能) 障害発生時の自動復旧 〇(Amazon CloudWatchとの連携で可能) 〇(AKSによる自動復旧) 費用の上限値設定 AWS Budgetにてアラート Azure monitorにてアラート ランタイムのモデル作成時と異なるリージョンへのデプロイ可否 〇 〇 ディザスタリカバリ × × おわりに
本投稿では、ストリーム型の機械学習システムをサービングする技術について、
Amazon SageMakerとAzure Machine Learningを比較した結果を紹介しました。
後編では、実際にストリーム型の機械学習システムを両クラウドでサービングして、本投稿で紹介した違いが現れること、
また、SEがどのような基準でAmazon SageMakerまたはAzure MLの利用を検討するべきかについて議論します。
- 投稿日:2020-11-18T04:07:20+09:00
rails new〜デプロイまでの学習レポート
はじめに
本記事は、ぼくが2020年7月〜10月までに学習した、Ruby及びRailsの内容を振り返るものです。
今年の2月くらいの段階ではHTMLもまるで書けないくらい無知だったのですが、
等の学習サイトや各種記事を参考にさせていただいて、なんとかデプロイまで漕ぎ着けることができました。なお現在の状況としては、
- 就職活動中(HTMLコーダー職志望)
- 学習を継続し、社内でのエンジニア登用を希望している
- フロントエンドとインフラに関心が強い
- Railsアプリの他に、Web制作に向けたWordPressの学習も行った
- フロント・バックともにそれらサイトで学習の継続を予定
というところです。
制作したアプリケーション
動いている実際のアプリはこちら。
使用言語はRuby、フレームワークはRuby on Railsです。AWS EC2サーバーにて、
- アプリケーションサーバ ▶︎ Puma
- Webサーバ ▶︎ Nginx
- データベース ▶︎ MySQL
という構成で動いています。
機能は現在
以上の2つのみ。
正直ポートフォリオと呼んでいいのかさえ怪しいものではあります。しかしながら、学習の目標を立てる際は「方向性だけわかるようにして機能は後回し、まずはデプロイまで一通りやる」ということを重点的に考えていたので、その目標自体は達成できました。本当に難しかったので、機能を書くより先にすませられたのは正直ホッとしています。
学習したこと
【1】Webサイトが動く大まかな仕組みについて
Rubyへの理解というよりは、Railsの仕組みに関して学ぶところが多かったように思えます。
フレームワーク側で効率化されている要素がかなり多く、大したRubyの知識がなくても何かしらの動作をするものは作れてしまうのが、すごいところでもあり、逆に油断してしまいそうだという印象です。RailsはMVCというデザインパターンを採用しており、
「扱う情報を定義したクラス内で処理を記述する」
「SQL文によってデータベースから情報を取得する」
という概要に触れただけでも、Webサイトの動作に必要な処理の大まかなイメージを掴めたのはよかったと思っています。【2】サーバーとデータベース
ユーザーからのリクエストを処理するWebサーバ(Nginx)と、Webサーバからのリクエストを受けてRailsを実行するアプリケーションサーバ(Puma)など、サーバーサイドの初歩的な技術を体験することができました。
AWS EC2で立てたインスタンスにNginxとPumaをインストールした構成にしており、データベースはRDSを使わずに、直接MySQLをインストールしてRailsと接続しています。
また、ターミナルからコマンドを使用したサーバーへのSSH接続も大変にいい経験でした。
FTPを使わなくてもサーバーに直接ログインし、viコマンドでファイルの書き換えなどを行うことができるようになりました。
作業の安全性を確保する上で、重要な知見になったと思います。機能が少ない段階で一度デプロイしようと思ったのは、
- 初めは難しいだろうから、先に乗り越えてしまおうと思ったから
- 機能を実装しても、Web上で動作できなければ人に見せられないから
- 一度デプロイしてしまえば、その後はコードを書くことに集中できると判断したから
- さらにその過程で、Capistorano等の自動デプロイの方法も勉強できるから
というのが理由です。
実際unicorn
サーバが起動できずに何日も浪費してしまうなど、ローカル環境構築以上につまづくことの多い部分でした。公開が無事に完了しただけでもまずはよかったと思っています。【3】フロントエンド
JavaScriptはProgateを一周しただけでしたが、少し苦手意識がありました。
処理が軽快になるというメリットもあって、動作させる部分はしばらくCSSアニメーションによって記述していたのですが、結果的にそれはjQueryを理解する大きな助けになりました。いずれにせよクラスやidを使って直接的に動作を指定するという点では、両者に違いはないと気がつきました。
同時にCSSの場合、モバイル(タップ操作)での動作が厳密には定義されていない部分があり、レスポンシブ対応する上ではjQueryでなければ書けないこともあるのだ、というのも興味深い発見でした。フロントエンドは現在特に関心を持っている分野であり、今後は基礎的なJavaScriptへの理解をもっと進めていきたいと考えています。
さらに、「jQueryによるDOM操作は規模が大きくなればなるほど面倒が増えていくだろう」という見当もついたため、今後はVue.jsによる柔軟な実装にも対応できるようになりたいと考えており、今後しばらくの学習目標として考えています。
今後の学習について
これらの経験を踏まえ、今後の個人学習は以下のような内容を考えています。
JavaScriptを初め、RubyやPHPなどの動的型付け言語の基本文法
- 復習的な内容にはなりますが、一旦インフラは置いておき、まずはプログラミング言語自体の理解をどんどん深めていきたいです。
- JavaScriptの理解から派生して、フロントならVue.jsやTypeScript、バックエンドはPHP等、学習内容を応用しながら順序立てて理解していきます。
HTML・CSSに関する更なる理解
- HTML5及びCSS3の機能の多さに驚きました。SVG素材がとても使いやすく、音声や動画も簡単に挿入できたり、文字の折りたたみや計算などもHTMLだけで記述できます。CSSではアニメーションをつけたり、Sassによる効率化も可能です。
- これらを学習するだけでもレベルアップできますし、併せてJavaScriptの学習も続ければ、すぐに複雑なサイトを作れるようになると思います。
デプロイの自動化
1.Circle CIやCapistranoを使った自動デプロイももちろんですが、今一番やりたいのはWordPressテーマのソースコードを変更した際、GitHubのmasterブランチにPushした時点で自動的に反映できるようにすることです。
Railsアプリで同様の自動化を行う入門もかねて、WordPressによるコーディング・フロントエンド学習を効率化したいと考えています。最後に
7月に前職を退職してから現在に至るまで、学習においては多数の方のお世話になっております。
Qiitaやブログ、オウンドメディア等にあるたくさんの記事を参考にさせていただきましたし、teratailで拙い質問に答えていただいたこともありました。皆様にはこの場を借りてお礼を申し上げます。
- 投稿日:2020-11-18T01:30:21+09:00
[OCI]goofysを利用してAWS S3からオブジェクト・ストレージに直接ファイルをコピーしてデータ移行してみた
はじめに
オブジェクト・ストレージをファイルシステムとしてマウントできるgoofysを利用して、AWSのS3バケットからOCIオブジェクト・ストレージに直接データを移行してみました。
この方法であれば、S3バケット内のファイルを一度どこかにダウンロードしてからアップロードする必要がないため、中間ストレージが不要になります。
安定した環境が必要な場合は、S3側にAWS Storage Gateway、OCIオブジェクト・ストレージ側にOracle Cloud Infrastructure Storage Gatewayを使用することをお勧めします。
事前準備
AWS側(データ移行元)
・S3バケット(ここではfrom-aws-bucketという名前です)
・S3バケットにアクセスできるユーザのアクセスキーとシークレットキー
OCI(データ移行先)
・オブジェクト・ストレージ・バケット(ここではociという名前です)
・オブジェクト・ストレージ・バケットにアクセスできるユーザの顧客秘密キー(アクセス・キーと秘密キー)
・インターネットアクセスが可能なコンピュート・インスタンス(今回はOSとしてOracle Linux 7.9を使用)1. goofysのインストールと設定
SSHクライアントからコンピュート・インスタンスにopcユーザでログインします。
goofysはGo、fuseを使用するので、事前にインストールします。
sudo yum install -y golang fusegoofysのバイナリをダウンロードします。
wget https://github.com/kahing/goofys/releases/latest/download/goofysgoofysをパスが通っているディレクトリに移動します。
sudo cp goofys /usr/local/binオーナーをrootに変更し、パーミッションを変更します。
sudo chown root:root /usr/local/bin/goofys sudo chmod 775 /usr/local/bin/goofysgoofysが使用する認証情報の設定ファイルを作成します。
sudo mkdir -p /root/.aws sudo vi /root/.aws/credentials/root/.aws/credentials[oci] aws_access_key_id = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA <- OCIユーザのアクセスキー aws_secret_access_key = BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB <- OCIユーザの秘密キー [aws] aws_access_key_id = CCCCCCCCCCCCCCCCCCCC <- AWSユーザのアクセスキー aws_secret_access_key = DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD <= AWSユーザのシークレットキー以上でgoofysを使用する準備が整いました。
2. goofysによるS3バケットとオブジェクト・ストレージ・バケットのマウント
マウントポイントを作成します。
・/mnt_o:OCI オブジェクト・ストレージ・バケット(oci)用のマウントポイント
・/mnt_a:AWS S3バケット(from_aws_bucket)用のマウントポイントsudo mkdir -p /mnt_o sudo chmod -R 777 /mnt_o sudo mkdir -p /mnt_a sudo chmod -R 777 /mnt_a/etc/fstabを編集します。
sudo vi /etc/fstab以下の2行をファイルの末尾に追加します。
goofys#oci /mnt_o fuse _netdev,allow_other,--dir-mode=0755,--file-mode=0666,--uid=1000,--gid=1000,--endpoint=https://<ネームスペース>.compat.objectstorage.<OCIリージョン識別子>.oraclecloud.com,--region=<OCIリージョン識別子>,--profile=oci 0 0 goofys#from-aws-bucket /mnt_a fuse _netdev,allow_other,--region=<AWSリージョン識別子>,--profile=aws 0 0mount -aでfstabの内容を反映し、OCIのオブジェクト・ストレージ・バケット oci を/mnt_oに、AWSのS3バケット from_aws_bucket を/mnt_aにマウントします。
sudo mount -a3. S3バケットからオブジェクト・ストレージ・バケットへのデータコピー
AWSのコンソールから、S3バケット from_aws_bucket の内容を確認します。
テスト用のPDFファイルが30個入っています。S3バケット from_aws_bucket をマウントした /mnt_a の内容を確認します。
[opc@iptest /]$ cd /mnt_a [opc@iptest mnt_a]$ ls -l total 63990 -rw-r--r--. 1 root root 2183884 Nov 17 14:36 50544 10.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:36 50544 11.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:36 50544 12.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:36 50544 13.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:36 50544 14.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:36 50544 15.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:36 50544 16.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:36 50544 17.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:36 50544 18.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:36 50544 19.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:36 50544 1.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:36 50544 20.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:36 50544 21.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:36 50544 22.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:36 50544 23.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:36 50544 24.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:36 50544 25.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:36 50544 26.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:36 50544 27.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:36 50544 28.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:36 50544 29.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:36 50544 2.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:36 50544 30.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:36 50544 3.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:36 50544 4.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:36 50544 5.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:36 50544 6.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:36 50544 7.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:36 50544 8.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:36 50544 9.pdfS3バケット from_aws_bucket の内容が表示されることを確認できました。
では、cpコマンドで /mnt_a 内のファイルを /mnt_o にコピーしてみます。
cp /mnt_a/* /mnt_oコピーが完了したら、オブジェクト・ストレージ・バケット oci をマウントした /mnt_o の内容を確認します。
[opc@iptest mnt_a]$ cd /mnt_o [opc@iptest mnt_o]$ ls -l total 63990 -rw-r--r--. 1 root root 2183884 Nov 17 14:39 50544 10.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:39 50544 11.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:39 50544 12.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:39 50544 13.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:39 50544 14.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:39 50544 15.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:39 50544 16.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:39 50544 17.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:39 50544 18.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:39 50544 19.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:39 50544 1.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:39 50544 20.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:39 50544 21.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:39 50544 22.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:40 50544 23.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:40 50544 24.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:40 50544 25.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:40 50544 26.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:40 50544 27.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:40 50544 28.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:40 50544 29.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:40 50544 2.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:40 50544 30.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:40 50544 3.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:40 50544 4.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:40 50544 5.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:40 50544 6.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:40 50544 7.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:40 50544 8.pdf -rw-r--r--. 1 root root 2183884 Nov 17 14:40 50544 9.pdf/mnt_a(=S3バケット from_aws_bucket )の内容が /mnt_o にコピーされました。
OCIのコンソールから、オブジェクト・ストレージ・バケット oci の内容を確認します。
S3バケット from_aws_bucket の内容がオブジェクト・ストレージ・バケット ociにコピーされていることが確認できました。まとめ
goofysを使用することで、AWSのS3バケットの内容を一度ダウンロードすることなく、OCIのオブジェクト・ストレージ・バケットに直接コピーしてデータを移行することができました。めでたし、めでたし。
ファイル数やファイルサイズによるパフォーマンスの変化についても、後日確認してみたいと思います。
参考文献