Con la puesta en marcha por parte de Bitbucket de su nueva herramienta ahora queda más fácil realizar una Integración Continua de manera directa desde nuestro repositorio hacia un ambiente en Elastic Beanstalk. Gracias a Bitbucket Pipelines ya podemos dejar de depender de infraestructura adicional como instancias con Jenkins instalado. En esta publicación nos vamos a enfocar de manera directa el modo de realizar un despliegue en un entorno elástico alojado en Amazon AWS.

No se va a mencionar qué es Bitbucket, qué es un repositorio o una rama de un repositorio. Solo se mencionará que Bitbucket Pipelines se puede activar en cualquier rama del repositorio de nuestro código fuente y que simplemente con un push, un pull o editando directamente desde Bitbucket se puede hacer el despliegue de manera automática y para lograr realizar la integración basta con tener un par de archivos en la raiz del repositorio. Acá vamos a usar el ejemplo publicado en la documentación de Bitbucket pero adaptado para hacer dicha tarea en un ambiente con PHP.

Lo primero es o bien clonar el repositorio de ejemplo o copiar los siguientes códigos en archivos nuevos. Como consejo los siguientes nombres de los archivos deberían mantenerse con el fin de tener estandarizado todo el concepto. Bitbucket Pipelines se basa como se mencionó antes en un par de archivos, uno de tipo YAML llamado bitbucket-pipelines.yml el cual contiene las instrucciones que se realizará para crear el artefacto que se va a subir y el cual será el que se despliegue en Elastic Beanstalk y el segundo, es un script en Python llamado beanstalk_deploy.py el cual es el que se encarga de subir el código y actualizar nuestro entorno. Este último es invocado por el archivo YAML.

El contenido del primer archivo es como esto:

# This is a sample build configuration for PHP.
# Check our guides at https://confluence.atlassian.com/x/VYk8Lw for more examples.
# Only use spaces to indent your .yml configuration.
# -----
# You can specify a custom docker image from Docker Hub as your build environment.
#image: phpunit/phpunit:5.0.3
image: python:3.5.1

pipelines:
  default:
    - step:
        script: # Modify the commands below to build your repository.
          - apt-get update # required to install zip
          - apt-get install -y zip # required for packaging up the application
          - pip install boto3==1.3.0
          - zip -r artifact.zip -x beanstalk_deploy.py bitbucket-pipelines.yml
          - python beanstalk_deploy.py # run the deployment script

En este primero lo que hace es actualizar la máquina - Docker - e instalar el paquete ZIP y a continuación va a comprimir el contenido del repositorio exceptuando los 2 archivos mencionados y por último invoca el archivo en Python para subir el archivo ZIP recién creado y actualizar nuestro ambiente elástico con ese archivo. Como se puede ver, se resalta el nombre de artifact, este nombre de archivo es opcional y puede ser cualquier otro.

El contenido del script en Python es como el siguiente:

# Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file
# except in compliance with the License. A copy of the License is located at
#
#     http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is distributed on an "AS IS"
# BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under the License.
"""
A Bitbucket Builds template for deploying
an application to AWS Elastic Beanstalk
Esta dirección de correo electrónico está protegida contra spambots. Necesita activar JavaScript para visualizarla.
v1.0.0
"""
from __future__ import print_function
import os
import sys
from time import strftime, sleep
import boto3
from botocore.exceptions import ClientError

"""
VERSION_LABEL es la etiqueta o nombre de la versión que se esta desplegando. Es lo que se ve al entrar en el entorno en la consola de Amazon
""" VERSION_LABEL = strftime("%Y%m%d%H%M%S")
BUCKET_KEY = os.getenv('APPLICATION_NAME') + '/' + VERSION_LABEL + \ '-bitbucket_builds.zip' def upload_to_s3(artifact): """ Uploads an artifact to Amazon S3 """ try: client = boto3.client('s3', region_name=os.getenv('AWS_REGION')) except ClientError as err: print("Failed to create s3 boto3 client.\n" + str(err)) return False try: client.put_object( Body=open(artifact, 'rb'), Bucket='elasticbeanstalk-' + os.getenv('AWS_REGION') + '-' + os.getenv('AWS_ACCOUNT_ID'), Key=BUCKET_KEY ) except ClientError as err: print("Failed to upload artifact to S3.\n" + str(err)) return False except IOError as err: print("Failed to access artifact.zip in this directory.\n" + str(err)) return False return True def create_new_version(): """ Creates a new application version in AWS Elastic Beanstalk """ try: client = boto3.client('elasticbeanstalk', region_name='us-east-1') except ClientError as err: print("Failed to create eb boto3 client.\n" + str(err)) return False try: response = client.create_application_version( ApplicationName='AppName', VersionLabel=VERSION_LABEL, Description='New build from Bitbucket', SourceBundle={ 'S3Bucket': Bucket='elasticbeanstalk-' + os.getenv('AWS_REGION') + '-' + os.getenv('AWS_ACCOUNT_ID'), 'S3Key': BUCKET_KEY }, Process=True ) except ClientError as err: print("Failed to create application version.\n" + str(err)) return False try: if response['ResponseMetadata']['HTTPStatusCode'] is 200: return True else: print(response) return False except (KeyError, TypeError) as err: print(str(err)) return False def deploy_new_version(): """ Deploy a new version to AWS Elastic Beanstalk """ try: client = boto3.client('elasticbeanstalk', region_name=os.getenv('AWS_REGION')) except ClientError as err: print("Failed to create eb boto3 client.\n" + str(err)) return False try: response = client.update_environment( ApplicationName='AppName', EnvironmentName='EnvName', VersionLabel=VERSION_LABEL, ) except ClientError as err: print("Failed to update environment.\n" + str(err)) return False print(response) return True def main(): " Your favorite wrapper's favorite wrapper " if not upload_to_s3('artifact.zip'): sys.exit(1) if not create_new_version(): sys.exit(1) # Wait for the new version to be consistent before deploying sleep(5) if not deploy_new_version(): sys.exit(1) if __name__ == "__main__": main()

En este archivo lo que se hace es obtener los datos de nuestro ambiente en AWS Elastic Beanstalk, para ello tenemos que generar unas variables de entorno, al estilo Apache; esto se realiza desde las configuraciones de Bitbucket:

Una vez allí veremos en el menú lateral izquierdo a Pipelines (en cuentas antiguas se debe activar, en las nuevas cuentas ya viene activado por defecto) y veremos la opción Environment Variables:

 

Al acceder a esta opción podremos agregar las variables de entorno:

Estas constan de un nombre, un valor y opcionalmente asegurarla. Para nuestro ejemplo y con el fin de que estas variables puedan ser obtenidas por el código en Python las vamos a definir de la siguiente manera:

  • AWS_ACCESS_KEY_ID
  • AWS_SECRET_ACCESS_KEY
  • AWS_REGION
  • AWS_ACCOUNT_ID

Las dos primeras están relacionadas con las credenciales de acceso del usuario en IAM el cual tenga permisos de acceso al Bucket predefinido para alojar las versiones de las aplicaciones en Elastic Beanstalk, si no tiene mucha idea de lo que se menciona en este parte se recomienda leer la documentación oficial de AWS. La tercera opción es la modificación que se va a realizar. AWS exigue que se pase la región en la cual se deba realizar el despliegue. Puede ver una lista completa de las regiones en este enlace. La última es el ID único de la cuenta de Amazon AWS la cual se puede ver en los detalles de la factura o en los detalles de la cuenta.

En este código se resaltan 3 nombres marcados con negrita: artifact, AppName y EnvName. El primero está relacionado con el nombre del archivo ZIP que configuramos previamente en el YAML y el cual es el que se va a subir. El segundo hace referencia al nombre de aplicación o Application Name definido al crear el ambiente en Elastic Beanstalk, el tercero es el nombre de nuestro entorno o Environment Name definido igualmente en el mismo proceso de creación del ambiente. Reemplazamos los anteriores con nuestros valores.

Con todo esto ya simplemente basta cualquier modificación para que nuestro código sea desplegado en Elastic Beanstalk de manera automática y sin tener que hacer uso de herramientas como Jenkins o similares.