Usando o Serverless Ruby no AWS Lambda para redimensionar imagens

Maarten van Vliet Blocked Desbloquear Seguir Seguindo 4 de janeiro

No último AWS ReInvent, foi anunciado que o AWS Lambda suportaria o Ruby como uma linguagem de tempo de execução. Eu estava ansioso para testar isso, a poderosa sintaxe e os recursos do Ruby são uma alegria para trabalhar e acoplar isso com o AWS Lambda. Achei que poderia ser aproveitado para um redimensionamento fácil do Lambda.

Comecei com a estrutura sem servidor, pois é uma maneira fácil de provisionar o Lambda. O objetivo é que, quando uma imagem é carregada em um bucket do S3, um Lambda seja iniciado, ele redimensione a imagem e, em seguida, carregue-a em outro bucket.

O primeiro passo é instalar serverless

 npm install -g serverless 
serverless create -t aws-ruby -p image-resizer

Isso cria o clichê básico de uma função Lambda framework ruby sem servidor. Ele vai criar dois arquivos, um handler.rb , o arquivo com a função real do Lambda vai chamar, e uma serverless.yml um arquivo que o quadro sem servidor usa para configurar e provisionar o lambda e serviços afiliados, tais como AWS S3 e AWS API Gateway

Primeiro, alteraremos a configuração serverless.yml . Adicionamos uma função handle_resize que chama a função ImageHandler.process em handler.rb sempre que um objeto é criado no bucket S3 your-images . Além disso, sob a chave do provedor, concedemos à função acesso ao S3 e usamos o tempo de execução do ruby2.5 .

Também adicionamos um recurso S3 resized-your-images , esse será o repositório para o qual movemos as imagens redimensionadas.

 service: image-resizer 
provider:
name: aws
runtime: ruby2.5
iamRoleStatements:
- Effect: Allow Action:
- s3:* Resource: "*"
functions:
handle_resize:
handler: handler.ImageHandler.process
events:
- s3:
bucket: your-images
event: s3:ObjectCreated:*
resources:
Resources:
ResizedImages:
Type: AWS::S3::Bucket
Properties:
BucketName: resized-your-images

Agora podemos implementar a função ImageHandler.process .

Escrevendo o lambda Ruby

Primeiro vamos adicionar um Gemfile por causa de duas dependências que precisamos usar. O AWS S3 SDK para recuperar e fazer upload de arquivos para o S3 e o mini_magick gem, um wrapper para o imagemagick.

 #Gemfile 
source 'https://rubygems.org'
 gem 'aws-sdk-s3', '~> 1.30.0' 
gem "mini_magick", '~> 4.9.0'

Em seguida, atualizamos o arquivo handler.rb .

 #handler.rb 
require 'uploaded_file'
 class ImageHandler 
def self.process(event:, context:)
event = event["Records"].first
bucket_name = event["s3"]["bucket"]["name"]
object_name = event["s3"]["object"]["key"]
file = UploadedFile.from_s3(bucket_name, object_name)
file.resize "100x100"
file.upload_file("resized-your-images", "resized_" + event["s3"]["object"]["key"] )
end
end

O ImageHandler.process é a função chamada pelo lambda e possui dois argumentos, o event , no nosso caso, um evento enviado pelo S3 sobre arquivos recém-criados, e o context , basicamente metadados sobre a função e seu ambiente.

A função de process recupera o bloco e a chave do objeto recém-criado do objeto de evento. Em seguida, ele chama a classe UploadedFile ainda não implementada. Essa classe recupera o objeto s3, redimensiona e envia para outro bucket. Esta classe é independente do lambda e, em geral, toda a lógica de negócios não deve ser vinculada ao Lambda. Isso facilita o uso do código em outros contextos e também permite testes mais fáceis. Por exemplo, posso testar o UploadedFile.from_s3(bucket_name, object_name) sem ter que simular todo o evento S3 Lambda.

Agora vamos implementar a classe UploadedFile.

 # uploaded_file.rb 
require "aws-sdk-s3"
require "mini_magick"
 class UploadedFile 
def self.from_s3(bucket_name, object_name)
s3 = Aws::S3::Resource.new()
object = s3.bucket(bucket_name).object(object_name)
tmp_file_name = "/tmp/#{object_name}"
object.get(response_target: tmp_file_name)
  UploadedFile.new(tmp_file_name) 
end
  def initialize(tmp_file) 
@tmp_file = tmp_file
end
  def resize(params) 
image = MiniMagick::Image.open(@tmp_file)
image.resize params
@resized_tmp_file = "/tmp/resized.jpg"
image.write @resized_tmp_file
end
  def upload_file(target_bucket, target_object) 
s3 = Aws::S3::Resource.new()
object = s3.bucket(target_bucket).object(target_object).upload_file(@resized_tmp_file)
end
end

O uploaded_file.rb é um rubi típico. Ele recupera o arquivo S3, armazena-o em um arquivo tmp. Chamamos mini_magick para redimensionar a imagem e, em seguida, fazer o upload do arquivo novamente.

Desdobramento, desenvolvimento

Ao implantar, precisamos ter certeza de que a função Lambda tenha acesso às dependências. A estrutura sem servidor criará um arquivo zip com o código e todas as dependências localmente e implantará isso na AWS. Portanto, é importante que executemos a mesma versão do ruby localmente no AWS, de modo que seria o ruby 2.5. Se você usar o rbenv , você pode usar o rbenv install 2.5.0 e o rbenv local 2.5.0 para definir a versão do Ruby para este projeto.

Em seguida, precisamos vender as dependências para que elas sejam incluídas no pacote. Executar o bundle install --path vendor/bundle , agora as dependências estão no diretório do vendor .

Antes de podermos implantar, ainda precisamos definir as credenciais para a AWS. Veja a documentação do Serverless Framework para mais informações.

Agora que isso é feito, você pode chamar sls deploy e a função lambda será implementada e os buckets S3 serão criados. Faça o upload de uma imagem para o intervalo " your-images e, momentos depois, uma versão redimensionada aparecerá no intervalo " resized-your-images . Se algo der errado, você pode seguir isso nos arquivos de log no AWS Cloudwatch. Haverá um fluxo de log para a função lambda. Você também pode chamar sls logs -f handle_resize para completar os logs.

Encontre o código no github