Sponsored by AppSignal
Would you like to see your link here? Contact us
Notesclub

AWS Lambda

livebooks/aws/lambda.livemd

AWS Lambda

Mix.install([
  {:aws, "~> 0.13"},
  {:ex_aws, "~> 2.5"},
  {:ex_aws_sts, "~> 2.3"},
  {:ex_aws_lambda, "~> 2.1"},
  {:req, "~> 0.3"},
  {:nx, "~> 0.6"},
  {:stb_image, "~> 0.6"},
  {:hackney, "~> 1.20"},
  {:sweet_xml, "~> 0.7"},
  {:kino, "~> 0.12"}
])

認証

access_key_id_input = Kino.Input.password("ACCESS_KEY_ID")
secret_access_key_input = Kino.Input.password("SECRET_ACCESS_KEY")
region_input = Kino.Input.text("REGION")

[
  access_key_id_input,
  secret_access_key_input,
  region_input
]
|> Kino.Layout.grid(columns: 3)
client =
  AWS.Client.create(
    Kino.Input.read(access_key_id_input),
    Kino.Input.read(secret_access_key_input),
    Kino.Input.read(region_input)
  )
auth_config = [
  access_key_id: Kino.Input.read(access_key_id_input),
  secret_access_key: Kino.Input.read(secret_access_key_input),
  region: Kino.Input.read(region_input)
]

Kino.nothing()

アカウントIDの取得

account_id =
  ExAws.STS.get_caller_identity()
  |> ExAws.request!(auth_config)
  |> then(& &1.body.account)

IAM ロールの定義

client
|> AWS.IAM.create_role(%{
  "RoleName" => "lambda-resnet-role",
  "AssumeRolePolicyDocument" =>
    Jason.encode!(%{
      "Statement" => [
        %{
          "Sid" => "STS202201051440",
          "Effect" => "Allow",
          "Principal" => %{
            "Service" => ["lambda.amazonaws.com"]
          },
          "Action" => "sts:AssumeRole"
        }
      ]
    })
})
client
|> AWS.IAM.create_policy(%{
  "PolicyName" => "lambda-resnet-role-policy",
  "PolicyDocument" =>
    Jason.encode!(%{
      "Version" => "2012-10-17",
      "Statement" => [
        %{
          "Effect" => "Allow",
          "Action" => [
            "cloudwatch:PutMetricData",
            "logs:CreateLogStream",
            "logs:PutLogEvents",
            "logs:CreateLogGroup",
            "logs:DescribeLogStreams"
          ],
          "Resource" => ["*"]
        }
      ]
    })
})
client
|> AWS.IAM.attach_role_policy(%{
  "RoleName" => "lambda-resnet-role",
  "PolicyArn" => "arn:aws:iam::#{account_id}:policy/lambda-resnet-role-policy"
})

ECRリポジトリー作成

region = Kino.Input.read(region_input)
image = "lambda-resnet"
fullname = "#{account_id}.dkr.ecr.#{region}.amazonaws.com/#{image}:latest"
client
|> AWS.ECR.create_repository(%{
  "repositoryName" => image
})
client
|> AWS.ECR.describe_repositories(%{})
client
|> AWS.ECR.set_repository_policy(%{
  "repositoryName" => image,
  "policyText" =>
    Jason.encode!(%{
      "Statement" => [
        %{
          "Sid" => "ECR202201051440",
          "Effect" => "Allow",
          "Principal" => %{
            "AWS" => ["*"]
          },
          "Action" => "ecr:*"
        }
      ]
    })
})

ECR リポジトリーへのイメージプッシュ

token =
  client
  |> AWS.ECR.get_authorization_token(%{})
  |> elem(1)
  |> Map.get("authorizationData")
  |> Enum.at(0)
  |> Map.get("authorizationToken")
[username, password] =
  token
  |> Base.decode64!()
  |> String.split(":")
System.cmd(
  "docker",
  [
    "login",
    "--username",
    username,
    "--password",
    password,
    fullname
  ]
)
System.cmd(
  "docker",
  [
    "build",
    "-t",
    image,
    "/lambda/resnet"
  ]
)
System.cmd(
  "docker",
  [
    "tag",
    image,
    fullname
  ]
)
System.cmd(
  "docker",
  [
    "push",
    fullname
  ]
)
client
|> AWS.ECR.list_images(%{
  "repositoryName" => image
})
|> elem(1)
|> Map.get("imageIds")
|> Kino.DataTable.new()

Lambda関数の作成

client
|> AWS.Lambda.create_function(%{
  "FunctionName" => "resnet",
  "PackageType" => "Image",
  "Code" => %{
    "ImageUri" => fullname
  },
  "Role" => "arn:aws:iam::#{account_id}:role/lambda-resnet-role",
  "MemorySize" => 1024,
  "Timeout" => 900,
  "Environment" => %{
    "Variables" => %{
      "LOG_LEVEL" => "debug"
    }
  }
})
AWS.Lambda.get_function(client, "resnet")

Lambda 関数呼出

binary =
  "https://raw.githubusercontent.com/pjreddie/darknet/master/data/dog.jpg"
  |> Req.get!()
  |> Map.get(:body)
binary
|> StbImage.read_binary!()
|> StbImage.to_nx()
|> Kino.Image.new()
"resnet"
|> ExAws.Lambda.invoke(%{"Payload" => Base.encode64(binary)}, %{})
|> ExAws.request!(auth_config)
|> then(& &1["body"]["predictions"])
|> Kino.DataTable.new()
image_input = Kino.Input.image("INPUT_IMAGE", format: :jpeg)
binary =
  image_input
  |> Kino.Input.read()
  |> Map.get(:file_ref)
  |> Kino.Input.file_path()
  |> File.read!()

"resnet"
|> ExAws.Lambda.invoke(%{"Payload" => Base.encode64(binary)}, %{})
|> ExAws.request!(auth_config)
|> then(& &1["body"]["predictions"])
|> Kino.DataTable.new()

ログ確認

log_stream_name =
  client
  |> AWS.CloudWatchLogs.describe_log_streams(%{
    "logGroupName" => "/aws/lambda/resnet"
  })
  |> elem(1)
  |> Map.get("logStreams")
  |> Enum.at(-1)
  |> Map.get("logStreamName")
client
|> AWS.CloudWatchLogs.get_log_events(%{
  "logGroupName" => "/aws/lambda/resnet",
  "logStreamName" => log_stream_name
})
|> elem(1)
|> Map.get("events")
|> Enum.map(& &1["message"])

Lambda関数削除

"resnet"
|> ExAws.Lambda.delete_function()
|> ExAws.request!(auth_config)

IAM ロール削除

client
|> AWS.IAM.detach_role_policy(%{
  "RoleName" => "lambda-resnet-role",
  "PolicyArn" => "arn:aws:iam::#{account_id}:policy/lambda-resnet-role-policy"
})
client
|> AWS.IAM.delete_policy(%{
  "PolicyArn" => "arn:aws:iam::#{account_id}:policy/lambda-resnet-role-policy"
})
client
|> AWS.IAM.delete_role(%{
  "RoleName" => "lambda-resnet-role"
})

ECR リポジトリー削除

image_ids =
  client
  |> AWS.ECR.list_images(%{
    "repositoryName" => image
  })
  |> elem(1)
  |> Map.get("imageIds")
client
|> AWS.ECR.batch_delete_image(%{
  "repositoryName" => image,
  "imageIds" => image_ids
})
client
|> AWS.ECR.delete_repository(%{
  "repositoryName" => image
})