Esta semana ha tocado desplegar una aplicación Net Core 3.1 en AWS Linux, vamos a ver como ha sido la experiencia.
Se trata de una aplicación API REST que compilaremos mediante CodeBuild, desplegaremos en un ASG desde CodeDeploy y orquestaremos el CI/CD desde CodePipeLine.
Una vez creada la infraestructura, en mi caso la hemos creado dede CloudFormation, vamos a añadir a nuestra solución los archivos necesarios para hacer la compilación y el despliegue (buildspec.yml y appspec.yml). Para organizar un poco los archivos he creado una carpeta que he llamado AWS con el siguiente contenido:
- buildspec-api.yml
version: 0.2
env:
variables:
PROJECT: myapi
phases:
install:
runtime-versions:
dotnet: 3.1
pre_build:
commands:
- echo Restore started on `date`
- dotnet restore ./${PROJECT}/${PROJECT}.csproj
build:
commands:
- dotnet build -c Release ./${PROJECT}/${PROJECT}.csproj
post_build:
commands:
- if [[ $CODEBUILD_BUILD_SUCCEEDING == 0 ]] ;then exit 1; fi
- dotnet publish -c release -r linux-arm64 -o ./build_output ./${PROJECT}/${PROJECT}.csproj --self-contained false
- cp -rf AWS/scripts/api ./build_output/scripts
- cp -rf AWS/appspecs/api/appspec.yml ./build_output
artifacts:
files:
- '**/*'
base-directory: ./build_output
discard-paths: no
El objetivo de este paso es generar un archivo zip con el restultado de nuestra publicación, en la raiz del zip ha de existir el archivo appspec.yml, y por ultimo los shell scripts que usa appspec.yml los coloco dentro de una carpeta "scripts". Como peculiaridad, la publicación tiene como destino una maquina linux arm 64.
- appspec.yml
version: 0.0
os: linux
files:
- source: /
destination: /usr/share/nginx/html/api
hooks:
AfterInstall:
- location: ./scripts/service_systemd.sh
timeout: 300
runas: root
- location: ./scripts/set_environement.sh
timeout: 300
runas: root
El contenido del archivo zip generado en la fase de build lo vamos a descargar dentro de nuestra instancia en la carpeta "/usr/share/nginx/html/api"
- service_systemd.sh
sudo cp -f /usr/share/nginx/html/api/scripts/api.service /etc/systemd/system/api.service
cd /etc/systemd/system/
sudo systemctl enable api.service
sudo systemctl start api.service
Con el script "service_systemd.sh" ejecutaremos nuestra aplicacion net core 3.1 con kestrel como un servicio: Copiamos el archivo api.service que contiene la definición del servicio y lo arrancamos.
- api.service
[Unit]
Description=My Api
[Service]
WorkingDirectory=/usr/share/nginx/html/api
ExecStart=/usr/bin/dotnet /usr/share/nginx/html/api/myapi.dll
Restart=always
RestartSec=10
KillSignal=SIGINT
SyslogIdentifier=myapi
User=root
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false
[Install]
WantedBy=multi-user.target
- set_environement.sh
export ASPNETCORE_ENVIRONMENT=DEPLOYMENT_GROUP_NAME
Con este script establecemos en valor de la variable del entorno de ASPNET al valor que nos proporciona CodeDeploy con el nombre del deploymentGroup, de esta forma enlacamos los entornos creados en AWS CodeDeploy con los de nuestra aplicación (appsettings.json)
El siguiente paso es configurar desde la consola de AWS nuestro PipeLine, hay mucha documentación en la red y para no alargar el post os dejo este video que desde el minuto 10:44 configura CodePipeline. Es bastante similar y sencillo, en mi caso recordar que despliego sobre un ASG no en ElasticBeanstalk
Un paso previo ha sido crear nuestra golden AMI con todo lo necesario para poder ejecutar nuestra aplicación, en este caso os enumero la instalación sobre una imagen base RHEL 8 Arm64
- CodeDeploy
- CloudWatch
- NGIX
- AWS CLI
- SystemManager
- Python 3
- Net Core
Algo que nos ocasionó bastantes problemas fue la configuració del proxy inverso en NGINX (https://docs.microsoft.com/es-es/aspnet/core/host-and-deploy/linux-nginx?view=aspnetcore-5.0), siempre obteniamos un error 502. Revisando el log de errores de nginx descubrimos que se trataba de un error de permisos, y buscando mucho por google dimos con la solución:
setsebool httpd_can_network_connect on
Si no usais proxy inverso recordar que por defecto Kestrel solo escucha en localhost:5000 (https://docs.microsoft.com/es-es/aspnet/core/fundamentals/servers/kestrel/endpoints?view=aspnetcore-5.0), si ejecutais el comando
sudo netstat -tnlp
fijaros que solo admite conexiones desde 127.0.0.1
Para permitir otras ips la manera que me ha parecido mas sencilla es añadir una nueva settings a nuestro archivo appSettings.json
"Urls": "http://*:5000"
la respuesta la obtuve de https://stackoverflow.com/questions/34212765/how-do-i-get-the-kestrel-web-server-to-listen-to-non-localhost-requests
Happy Coding!!