{"id":429,"date":"2019-05-17T15:58:41","date_gmt":"2019-05-17T13:58:41","guid":{"rendered":"https:\/\/blog.besharp.it\/?p=429"},"modified":"2021-03-17T12:35:56","modified_gmt":"2021-03-17T11:35:56","slug":"come-creare-ci-cd-pipeline-flessibili-su-aws-con-fargate-e-sqs","status":"publish","type":"post","link":"https:\/\/blog.besharp.it\/it\/come-creare-ci-cd-pipeline-flessibili-su-aws-con-fargate-e-sqs\/","title":{"rendered":"Come creare CI\/CD Pipeline flessibili su AWS con Fargate e SQS"},"content":{"rendered":"
L\u2019utilizzo di <\/span>Pipeline per il deploy automatico del codice<\/b> \u00e8 ormai una feature quasi imprescindibile di ogni progetto di sviluppo in Cloud, in quanto il concetto stesso di architettura scalabile richiede che le macchine virtuali (o i container), che vengono avviati sul Cloud per gestire i picchi di traffico, utilizzino la versione pi\u00f9 aggiornata del codice. Inoltre, la creazione di una pipeline automatica<\/span> libera i DevOps dalla gestione manuale<\/b> di AMIs e Docker images, oltre a eliminare la possibilit\u00e0 di \u201cerrori umani\u201d in fase di deploy.<\/span><\/p>\n AWS mette a disposizione dei DevOps uno strumento molto potente per la creazione di Pipeline automatiche: <\/span>AWS CodePipeline.<\/b> Questo servizio totalmente <\/span>managed<\/span><\/i> funziona come un <\/span>orchestrator per una pipeline di CI\/CD<\/b> con funzionalit\u00e0 analoghe a quelle offerte da altri servizi come Jenkins che per\u00f2 vanno installati su un’istanza EC2 e, pertanto, oltre a non essere in alta affidabilit\u00e0, richiedono un effort significativo di configurazione e manutenzione.<\/span><\/p>\n Il flusso pi\u00f9 comune di una AWS CodePipeline \u00e8 composto da tre step:<\/span><\/p>\n Sebbene le features di AWS CodePipeline siano sufficienti per gli use case pi\u00f9 comuni, alcune necessit\u00e0 particolari richiedono di sviluppare uno o pi\u00f9 step personalizzati, cos\u00ec da avere maggiore flessibilit\u00e0. In questo articolo vedremo come \u00e8 possibile <\/span>creare una pipeline automatica in grado di eseguire la build di tutti i branch di un repo git hostato su AWS CodeCommit.<\/b><\/p>\n Molti progetti, in particolare quelli di dimensioni ragguardevoli, utilizzano git flow o un flusso analogo per organizzare il repository. <\/span><\/p>\n Questo fa s\u00ec che vi siano due o pi\u00f9 branch (e.g. production, staging, development) contenenti il codice effettivamente deployato sui relativi ambienti e un gran numero di branch feature, contenenti le singole feature in fase di sviluppo assegnate ai rispettivi developer e team, che una volta completate vengono integrate in development e testate. \u00a0<\/span><\/p>\n Tuttavia molto spesso non risulta possibile eseguire l\u2019intera suite di test automatici direttamente dalle workstation degli sviluppatori, sia per motivi di tempi, che per la necessit\u00e0 di testare la sempre crescente integrazione del codice coi vari servizi SaaS di AWS. Per ovviare a questi problemi e ridurre gli errori di integrazione risulterebbe molto comodo poter lanciare direttamente la build e la suite di test ad ogni commit sulle singole features branch tramite AWS CodeBuild, invece che solamente al momento del merge della feature in dev attraverso la CodePipeline appositamente creata per questo ambiente. <\/span><\/p>\n Purtroppo al momento AWS CodePipeline non supporta il source da sorgenti multiple; \u00e8 infatti necessario specificare sia il repo che il branch. Per risolvere il problema, in beSharp abbiamo sviluppato una soluzione creativa sfruttando la potenza di <\/span>CloudWatch Events, SQS e Fargate.<\/b><\/p>\n CloudWatch Rules<\/b>: il servizio di AWS che consente di creare regole per eseguire operazioni o in risposta ad eventi riguardanti l\u2019account AWS, come ad esempio l\u2019accensione di una EC2 o, nel nostro caso, un push su un repo CodeCommit, oppure ad intervalli di tempo fissati.<\/span><\/p>\n SQS FIFO: i<\/b>l servizio di code completamente gestito ed in alta affidabilit\u00e0 offerto da AWS<\/span>. <\/b>Nel nostro caso abbiamo usato la versione First In First Out (FIFO) in modo da essere certi di conservare l\u2019ordine dei messaggi.<\/span><\/p>\n Fargate:<\/b> \u00a0<\/b>Il terzo componente della soluzione \u00e8 un container Docker deployato tramite Fargate (ECS), il nuovo servizio di AWS che consente di avviare container as a service<\/em>, senza doversi occupare della gestione dell\u2019infrastruttura sottostante. <\/span><\/p>\n In modo analogo al funzionamento standard di AWS CodePipeline abbiamo usato CloudWatch Rules per preparare una regola che viene triggerata al momento del push da parte di uno sviluppatore su uno qualsiasi dei branch. La regola ha due azioni configurate: <\/span><\/p>\n la prima accoda un messaggio in una coda SQS,\u00a0 mentre la seconda avvia il container Fargate. Il messaggio inserito nella coda \u00e8 il json<\/em> che descrive l\u2019intero evento che ha avviato l\u2019esecuzione della CloudWatch Rule e che contiene il nome del repo, il nome del branch e l\u2019id del commit appena inviato dallo sviluppatore.<\/span><\/p>\n L\u2019event pattern della regola sar\u00e0 simile a questo:<\/span><\/p>\n La coda SQS FIFO <\/strong>contiene perci\u00f2 i messaggi corrispondenti agli eventi di push del codice sul repository e viene consumata dai container Fargate. Per evitare che messaggi corrotti possano venire ri-processati all\u2019infinito, abbiamo aggiunto una dead letter queue<\/strong> dove i messaggi vengono trasferiti dopo due tentativi di lettura falliti. <\/span><\/p>\n Una volta avviato dalla CloudWatch Rule, il container Fargate legge i messaggi dalla coda,<\/strong> esegue il pull del commit dal repository CodeCommit, salva il bundle compresso del codice su s3 ed infine lancia AWS CodeBuild coi parametri corretti.<\/span><\/p>\n Il container Docker \u00e8 stato creato usando il Dockerfile:<\/strong><\/span><\/p>\n Come si pu\u00f2 vedere sono richiesti solo pacchetti standard di bash, oltre alla AWS CLI. Lo script codecommit_source.sh<\/em> viene avviato al momento dell\u2019accensione del container, ed esegue la logica appena descritta.<\/span><\/p>\n Un codecommit_source.sh<\/strong> di esempio \u00e8 mostrato qui sotto:<\/span><\/p>\n Infine, chi gestisce il source code dovr\u00e0 aver cura di creare\/modificare il buildspec<\/strong> in modo da salvare gli output della build su S3 con un nome facilmente leggibile.<\/span><\/p>\n La soluzione qui riportata pu\u00f2 essere facilmente modificata per funzionare anche in caso di account multipli.<\/strong> Ad esempio possono essere presenti due account: il primo account (\u201cmaster\u201d) contenente l\u2019ambiente di produzione e i repos mentre il secondo gli ambienti di staging\/development e le pipeline. Per far ci\u00f2 \u00e8 necessario aggiungere un ruolo<\/em> all’account \u201cmaster\u201d che possa essere assunto da staging in modo da fare il pull dei repository. Infine sar\u00e0 anche necessario configurare event bus su entrambi gli account in modo da condividere i messaggi di prod relativi ai repo con l\u2019account di sviluppo.<\/span><\/p>\n Per concludere AWS CodePipeline \u00e8 uno strumento molto potente, ma per alcuni casi di uso non \u00e8 sufficiente e va perci\u00f2 affiancato a soluzioni custom come quella proposta che sono facilmente configurabili usando l\u2019ampia suite di servizi messi a disposizione da AWS.<\/span><\/p>\n Vuoi raccontarci di una tua soluzione di CD\/CI innovativa o avere ulteriori infomazioni su quella proposta in questo articolo? Non esitare a commentare e\/o a contattarci!<\/strong><\/span><\/p>\n","protected":false},"excerpt":{"rendered":" L\u2019utilizzo di Pipeline per il deploy automatico del codice \u00e8 ormai una feature quasi imprescindibile di ogni progetto di sviluppo […]<\/p>\n","protected":false},"author":9,"featured_media":432,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[241],"tags":[289,293,263,343],"class_list":["post-429","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devops","tag-amazon-route-53","tag-aws-codedeploy","tag-aws-lambda","tag-mongodb"],"yoast_head":"\n\n
Servizi utilizzati per la soluzione:<\/h2>\n
<\/em><\/p>\n
Schema dell\u2019infrastruttura della soluzione proposta<\/span><\/em><\/h5>\n
{<\/span>\r\n \u00a0\"source\": [<\/span>\r\n \u00a0\u00a0\u00a0\"aws.codecommit\"<\/span>\r\n \u00a0],<\/span>\r\n \u00a0\"detail-type\": [<\/span>\r\n \u00a0\u00a0\u00a0\"CodeCommit Repository State Change\"<\/span>\r\n \u00a0],<\/span>\r\n \u00a0\"resources\": [<\/span>\r\n \u00a0\u00a0\u00a0\"arn:aws:codecommit:eu-west-1:<ACCOUNT_ID>:<REPOSITORY>\",<\/span>\r\n \u00a0\u00a0\u00a0...<\/span>\r\n \u00a0],<\/span>\r\n \u00a0\"detail\": {<\/span>\r\n \u00a0\u00a0\u00a0\"event\": [<\/span>\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\"referenceCreated\",<\/span>\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\"referenceUpdated\"<\/span>\r\n \u00a0\u00a0\u00a0]<\/span>\r\n \u00a0}<\/span>\r\n}<\/span>\r\n<\/pre>\n
FROM ubuntu:16.04\r\nRUN apt-get update\r\nRUN apt-get install wget -y\r\nRUN apt-get install numactl -y\r\nRUN apt-get install jq -y\r\nRUN apt-get install zip -y\r\nRUN apt-get install git -y\r\nRUN apt-get install software-properties-common -y\r\nRUN add-apt-repository ppa:jonathonf\/python-3.6 -y\r\nRUN apt-get update\r\nRUN apt-get install python3.6 -y\r\nRUN wget https:\/\/bootstrap.pypa.io\/get-pip.py\r\nRUN python3.6 get-pip.py\r\nRUN pip3.6 install awscli --upgrade\r\nRUN pip3.6 install boto3\r\nRUN mkdir \/pipeline_source\r\nWORKDIR \/pipeline_source\r\nADD .\/codecommit_source.sh \/pipeline_source\/codecommit_source.sh\r\nRUN chmod +x \/pipeline_source\/codecommit_source.sh\r\nCMD \/pipeline_source\/codecommit_source.sh<\/pre>\n
#!\/bin\/bash\r\nset -Eeuxo pipefail\r\n\r\nMESSAGE=$(aws sqs receive-message --queue-url https:\/\/sqs.eu-west-1.amazonaws.com\/<account-id>\/custom-codecommit-events.fifo --wait-time-seconds 20)\r\nRECEIPT_HANDLE=$(echo $MESSAGE | jq -r '.Messages | .[] | .ReceiptHandle')\r\n\r\naws sqs delete-message --queue-url https:\/\/sqs.eu-west-1.amazonaws.com\/<account-id>\/custom-codecommit-events.fifo --receipt-handle $RECEIPT_HANDLE\r\n\r\nif [ -n \"$MESSAGE\" ]\r\nthen\r\nEVENT=$(echo $MESSAGE | jq -r '.Messages | .[] | .Body | fromjson')\r\nREPOSITORY_NAME=$(echo $EVENT | jq -r '.detail | .repositoryName')\r\nCOMMIT_ID=$(echo $EVENT | jq -r '.detail | .commitId')\r\nBRANCH_NAME=$(echo $EVENT | jq -r '.detail | .referenceName')\r\nREPO_URL=https:\/\/git-codecommit.eu-west-1.amazonaws.com\/v1\/repos\/$REPOSITORY_NAME\r\ngit config --global credential.helper '!aws codecommit credential-helper $@'\r\ngit config --global credential.UseHttpPath true\r\ngit clone --depth 10 --branch $BRANCH_NAME $REPO_URL\r\n\r\ncd $REPOSITORY_NAME\r\ngit checkout $COMMIT_ID\r\nrm -rf .git \r\nzip -r ..\/$COMMIT_ID.zip .\r\ncd ..\r\n\r\nrm -rf $REPOSITORY_NAME\r\n\r\nif [ -s $COMMIT_ID.zip ]\r\nthen\r\nCODEBUILD_PROJECT=$REPOSITORY_NAME\r\n\r\nif [ $BRANCH_NAME != \"test\" ] && [ $BRANCH_NAME != \"develop\" ] && [ $BRANCH_NAME != \"staging\" ] \r\nthen \r\n\r\naws s3 cp $COMMIT_ID.zip s3:\/\/$CODE_BUCKET\/$REPOSITORY_NAME\/$BRANCH_NAME\/$COMMIT_ID.zip\r\necho s3:\/\/$CODE_BUCKET\/$REPOSITORY_NAME\/$BRANCH_NAME\/$COMMIT_ID.zip\r\naws codebuild start-build --project-name $CODEBUILD_PROJECT --environment-variables-override name=COMMIT_ID,value=$COMMIT_ID,type=PLAINTEXT --source-type-override S3 --source-location-override $CODE_BUCKET\/$REPOSITORY_NAME\/$BRANCH_NAME\/$COMMIT_ID.zip --artifacts-override type=NO_ARTIFACTS\r\n\r\nfi\r\nfi\r\nelse\r\necho \"no message in queque\"\r\nfi<\/pre>\n