Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatize deployment script #2

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ node_modules/
# Local files
terraform.tfvars
ip
ips
elb_dns
src.zip
key.pem
58 changes: 57 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Tanto para escalar horizontalmente como para agregar una instancia de Redis, cad
## Setup
### AWS
- Con el [pack estudiantil de GitHub](https://education.github.com/pack), crear una cuenta estudiantil en [AWS](https://aws.amazon.com/).
- Entrar en IAM y crear un usuario "Terraform" con el grupo de permisos necesarios (AmazonEC2FullAccess, AmazonElastiCacheFullAccess).
- Entrar en IAM y crear un usuario "Terraform" con el grupo de permisos necesarios (AmazonEC2FullAccess, AmazonElastiCacheFullAccess, AmazonS3FullAccess).
- Desde IAM, generar un par de credenciales (key/secret) para ese usuario.
- (sugerido) Para facilitar el deployment, la propuesta es que creen un bucket en S3 en donde suban un zip con el código del servicio (`app.js`, `config.js`, `package.json` y `package-lock.json`), y luego cada instancia se encargará de bajarlo, descomprimirlo y ejecutarlo. Para esto entonces:
- Ir a S3 y crear un bucket. Por simplicidad, recomiendo que el bucket acepte lecturas de cualquiera, pero no escrituras. Pueden habilitar el versionado de los objetos en el bucket si quieren, pero no es importante.
Expand Down Expand Up @@ -92,3 +92,59 @@ terraform validate
# Lista los providers instalados. Para este tp, deben ser al menos "aws" y "template"
terraform providers
```

## Correr los servidores
### TL;DR
Existe el script `start.sh` en la raíz del proyecto para crear la infraestructura y correr los servidores correspondientes.

> **IMPORTANTE:** Es necesario tener instalado el [`aws-cli`](https://docs.aws.amazon.com/es_es/cli/latest/userguide/cli-chap-welcome.html) y [configurado](https://docs.aws.amazon.com/es_es/cli/latest/userguide/cli-config-files.html) con las credenciales correspondientes, donde además [se utiliza un perfil](https://docs.aws.amazon.com/es_es/cli/latest/userguide/cli-multiple-profiles.html) llamado `terraform`. Además, en el mismo se utiliza el binario de `terraform`, asumiendo que el mismo se encuentra en `~`. Por último, tal como se explicó antes, se asume que existe un bucket de S3 llamado `tp-arquitecturas`, al cual tiene acceso dicho usuario.

### Explicación
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Me encanta la documentación!

Lo único, qué opinás de mover la explicación detallada de cómo funciona el script al script mismo? O sea, comentarios arriba de cada línea explicando que hacen por ejemplo (en español está bien por mí, aunque la mayor parte del código esté en inglés), para no tener el código casi duplicado como queda así, que si alguien modifica cualquier cosa del script tiene que venir también a cambiar el README. El TL;DR sí me parece bien que esté acá para que el script tenga visibilidad.

Lo primero que hay que hacer es crear la infraestructura:
```sh
terraform apply -auto-approve
```
> **NOTA:** El flag `-auto-approve` involucra que no se pida la aprobación del plan. **Utilizar bajo su propia responsabilidad**.

> **IMPORTANTE:** Cabe destacar que en el archivo `python.tf` se especifica un comando para copiar la IP con la que se creó la instancia de python a un archivo local. Allí mismo también hay un comando que copia dicha IP al archivo `config.js` de la app node.

Una vez que la infraestructura está creada, es importante notar que la misma tiene corriendo el proceso node (Notar que se corre el archivo `node_user_data.sh` al levantar la infraestructura, que es quien se encarga de comenzar dicho proceso) pero no el proceso python. Es importante entonces iniciar el proceso python en el servidor correspondiente:
```sh
cd python && ./start
```

Además es importante también notar que el código que comenzó a correr node posee una URL inválida del servidor python. Es por ello que es importante volver a zippear la aplicación:
```sh
cd node
./zip
```

Luego, el archivo `src.zip` resultante debe ser subido al bucket correspondiente (especificado tanto en el `variables.tf` como en el script `update` de la carpeta `node`). Esto se puede hacer a mano, o bien utilizando el `aws-cli`:
```sh
aws s3 cp src.zip s3://tp-arquitecturas/src.zip --profile terraform
```
> **NOTA:** Aquí se está utilizando el perfil `terraform` del `aws-cli`. Los mismos se pueden [definir fácilmente](https://docs.aws.amazon.com/es_es/cli/latest/userguide/cli-multiple-profiles.html).

Luego, para que los cambios tomen efecto, hay que updatear el código del proceso node en su servidor. Para ello se puede obtener las IPs de las instancias de los nodos de node desde la consola de EC2 de AWS (bajo el nombre "IPv4 Public IP"), o bien programáticamente:
```sh
aws ec2 describe-instances --profile terraform --query "Reservations[*].Instances[*].PublicIpAddress" --filters "Name=tag-value,Values=tp_arqui_node_asg_instance" --output=text | tr '\t' '\n' > ips
```

Finalmente, con dichas IPs, se deben updatear todos los servidores node, bien llamando al script `update` a mano para cada IP, o bien:
```sh
while IFS='' read -r ip <&3 || [[ -n "$ip" ]]; do
./update "$ip"
done 3< ips
```
> **NOTA:** El `3` indica el file descriptor a utilizar para correr dicho loop, de forma de que no se interceda con el `stdin`. Para más información, [ver aquí](https://stackoverflow.com/questions/37168048/bash-command-runs-only-once-in-a-while-loop).

### Verificación
Una vez levantados los servidores, se puede verificar su correcto funcionamiento utilizando la URL que se encuentra dentro del archivo `elb_dns` de la carpeta `node` y pegándole:
```sh
curl http://<elb_dns_url>
```

Lo cual chequeará que la app node funciona. A su vez, si se quiere ver que la misma se puede comunicar con el servidor python es necesario utilizar:
```sh
curl http://<elb_dns_url>/remote
```
2 changes: 1 addition & 1 deletion node.tf
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ resource "aws_elb" "node_asg_elb" {
}

provisioner "local-exec" {
command = "echo ${aws_elb.node_asg_elb.dns_name} >> node/elb_dns"
command = "echo ${aws_elb.node_asg_elb.dns_name} > node/elb_dns"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Este cambio ya está hecho en master, qué raro que siga apareciendo en el diff... Podrías hacer un rebase de la branch porfa (o desde una nueva branch, para no modificar lo que hicieorn uds para su tp)?

}
}

Expand Down
3 changes: 2 additions & 1 deletion node/remote
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ else
COMMAND="${@:2}"
fi

ssh -i $DIR/../key.pem ec2-user@$IP -- $COMMAND
# The -o StrictHostKeyChecking=no is to avoid asking for permission to add the $IP to the list of known hosts
ssh -o StrictHostKeyChecking=no -i $DIR/../key.pem ec2-user@$IP -- $COMMAND
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Genial jajaja 👍

5 changes: 3 additions & 2 deletions node/update
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ echo "using IP $IP..."

$DIR/stop $IP

# The -o for the unzip command overrides if there's any existing file
$DIR/remote $IP "\
echo 'Downloading updated src' && \
curl https://s3.amazonaws.com/tp-arqui-node-app-src/src.zip > src.zip && \
curl https://s3.amazonaws.com/tp-arquitecturas/src.zip > src.zip && \
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Por favor, hacer que la url se lea de alguna configuración (por ejemplo, un archivo local que se llame source-location o similar)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahí estoy haciendo que lo lea de una variable de entorno. No sé si es posible hacer que esa misma se use en el variables.tf también, tenés idea?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sí, deberías poder hacer que el variables.tf lea del archivo usando una de las funciones de interpolación (file, como está hecho en esta línea). Nunca probé usarlo en el default del variables.tf, pero imagino que está soportado, no veo motivo por el que no lo estaría jajaja.

echo 'Unpacking src code' && \
unzip src.zip && \
unzip -o src.zip && \
echo 'Installing dependencies' && \
npm i"

Expand Down
11 changes: 11 additions & 0 deletions node/update-all
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/bash

set -e

DIR="$(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

IPS=`cat $DIR/ips`

while IFS='' read -r ip <&3 || [[ -n "$ip" ]]; do
$DIR/update $ip
done 3< ips
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Por favor, agregar un salto de línea al final para que GitHub no ponga símbolos feos en el diff 😅

5 changes: 5 additions & 0 deletions python.tf
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ resource "aws_instance" "python" {
command = "echo ${aws_instance.python.public_ip} > python/ip"
}

# Use the python app IP in node code
provisioner "local-exec" {
command = "sed -E \"s/(remoteBaseUri:)[^,]*,/\\1 'http:\\/\\/$(cat python/ip):3000',/\" node/config.js > node/temp.js && mv node/temp.js node/config.js"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sugerencia: Mejorar esto para que use el parámetro -i de sed en vez de crear un archivo temporal.

(tengo que hacer lo mismo en el paso en que configuro datadog dentro del user data yo, je)

}

# Environment setup
provisioner "remote-exec" {
inline = [
Expand Down
3 changes: 2 additions & 1 deletion python/remote
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ COMMAND=$@
DIR="$(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
IP=`cat $DIR/ip`

ssh -i $DIR/../key.pem ec2-user@$IP -- $COMMAND
# The -o StrictHostKeyChecking=no is to avoid asking for permission to add the $IP to the list of known hosts
ssh -o StrictHostKeyChecking=no -i $DIR/../key.pem ec2-user@$IP -- $COMMAND
29 changes: 29 additions & 0 deletions start.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/bin/bash

# Create infrastructure. This also changes the python IP in the node app so that they can communicate
echo "##### Applying terraform infrastructure #####"
~/terraform apply -auto-approve
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Preferiría sacar el ~/ y suponer que terraform está agregado al path ya, con lo que esta línea quedaría directamente

terraform apply -auto-approve


# Start the python app that is not started in the terraform apply command
echo "##### Starting python app #####"
cd python && ./start
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Esto se puede cambiar por algo tipo

$DIR/python/start

y así funciona independendientemente de dónde esté parada la terminal al ejecutar el script (ver otros scripts sobre cómo setearla).


cd ../node
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Por favor, seteá una variable $DIR como en los otros scripts y usala, para que se pueda correr desde cualquier lado


# Get node instances IPs with the AWS CLI. It returns a tab-separated string with the IPs, so we transform them to end-lines
echo "##### Getting node instances IPs #####"
aws ec2 describe-instances --profile terraform --query "Reservations[*].Instances[*].PublicIpAddress" --filters "Name=tag-value,Values=tp_arqui_node_asg_instance" --output=text | tr '\t' '\n' > ips

# Zip the source code of the node apps with the correct python IP
echo "##### Zipping node source code #####"
./zip
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usar un path absoluto con $DIR

echo "##### Uploading node source code to S3 bucket #####"
aws s3 cp src.zip s3://tp-arquitecturas/src.zip --profile terraform
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Por favor, hacer que la url del código se lea de alguna configuración (archivo local, variable de entorno o argumento)


# Update the node instances code with the src code uploaded to S3
while IFS='' read -r ip <&3 || [[ -n "$ip" ]]; do
echo "##### Updating node source code of instance with IP $ip #####"
./update "$ip"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usar paths absolutos con $DIR

done 3< ips

cd ..
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Por favor, agregar salto de línea al final del archivo (para evitar el siguiente símbolo feo en el diff de github nomás jajaja)
image

8 changes: 4 additions & 4 deletions variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,17 @@ variable "private_key_location" {
}

variable "vpc_id" {
default = "vpc-7563400d"
default = "vpc-d7cec3ac"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Por favor, rollbackear los cambios de este archivo

}

variable "ami_id" {
default = "ami-1853ac65"
default = "ami-14c5486b"
}

variable "node_version" {
default = "8.11.2"
default = "8.11.1"
}

variable "src_location" {
default = "https://s3.amazonaws.com/tp-arqui-node-app-src/src.zip"
default = "https://s3.amazonaws.com/tp-arquitecturas/src.zip"
}