{"id":2085,"date":"2021-01-08T12:21:25","date_gmt":"2021-01-08T11:21:25","guid":{"rendered":"https:\/\/blog.besharp.it\/?p=2085"},"modified":"2023-03-29T15:49:59","modified_gmt":"2023-03-29T13:49:59","slug":"nuove-istanze-aws-ec2-mac-ci-cd-come-banco-di-prova","status":"publish","type":"post","link":"https:\/\/blog.besharp.it\/it\/nuove-istanze-aws-ec2-mac-ci-cd-come-banco-di-prova\/","title":{"rendered":"Nuove istanze AWS EC2 Mac: CI\/CD come banco di prova"},"content":{"rendered":"\n
Durante l’ultimo AWS re:Invent, AWS ha fatto uno degli annunci pi\u00f9 discussi, e questo \u2014 almeno sulla carta \u2014 lascia spazio a nuovi possibili scenari di sviluppo: le istanze AWS EC2 Mac! A questo punto \u2014 assumendo che non abbiate conoscenza delle specifiche hardware delle istanze Amazon EC2 Mac \u2014 potreste chiedervi quali siano i tagli supportati. Beh, per quanto riguarda l’immediato, scordatevi di poter scegliere: AWS per ora rende disponibile solo un taglio di istanza Mac. Le specifiche hardware ci dicono che le istanze mac1.metal sono alimentate da un processore Intel Coffee Lake a 3.2 GHz \u2014 potenziabile sino a 4.6 GHz \u2014 e 32 GiB di memoria. Come spiegato da Jeff Barr nel blog di AWS, le istanze girano in VPC, includono Elastic Network Adapter, e sono ottimizzate per la comunicazione con volumi EBS, infine supportano operazioni intensive di I\/O.<\/p>\n\n\n\n Durante la mia routine giornaliera, il mio partner di lavoro \u00e8 un laptop macOS che ho prontamente aggiornato al nuovo sistema operativo Apple: Big Sur. Di fatto ad oggi non ho notato particolari miglioramenti, tuttavia mantenere il sistema aggiornato rimane una best practice, almeno per quanto riguarda il computer di lavoro. In questo senso le Istanze AWS EC2 Mac ci sono fornite con una limitazione: supporto solo per OS Mojave e Catalina. Le AMI di questi sistemi ci vengono fornite con gi\u00e0 preinstallati AWS CLI, tool da riga di comando per XCode, Homebrew e agent di SSM. AWS sta lavorando attivamente al supporto di Big Sur e anche ai Chip Apple M1.<\/p>\n\n\n\n Ora, diamo uno sguardo a ci\u00f2 che ci interessa di pi\u00f9: un caso d’uso pratico!<\/p>\n\n\n\n Ho cominciato la mia carriera lavorativa come sviluppatore, e immagino che diversi sviluppatori abbiano pensato, a loro volta, ad un’associazione tra questo annuncio e la possibilit\u00e0 di sviluppare, testare e firmare applicazioni macOS e iOS in cloud.<\/p>\n\n\n\n Durante l’ultimo anno, il mio team sta sviluppando un’applicazione Desktop Open-Source che gestisce credenziali in locale per permettere l’accesso ad Ambienti complessi in Cloud. Il nostro progetto \u00e8 scritto in Typescript, interpretato da Node.js. Utilizziamo Angular come framework di sviluppo, che viene fatto girare on-top ad Electron per garantirci compatibilit\u00e0 multi-piattaforma<\/p>\n\n\n\n Electron viene fornito di un sistema di compilazione nativo, chiamato Electron-Builder, che permette di aggiungere script personalizzati di build al file package.json, file che contiene inoltre, come di consueto, le dipendenze di progetto. Abbiamo creato diversi script personalizzati per generare i binari Linux, Windows e macOS.<\/p>\n\n\n\n Per sviluppare il binario macOS, lo script deve avere accesso ad un Certificato di Firma e alla Password di Autenticazione (Notarisation) Apple. Questi permettono, rispettivamente, di firmare e autenticare il binario. Abitualmente siamo soliti salvare dati sensibili come i segreti nel nostro Keychain di macOS, lanciare gli script di build in locale e caricare manualmente gli artefatti sul nostro repository di GitHub, infine creare una nuova Release. Questo processo \u00e8, per sommi capi, una pratica comune adottata da molti sviluppatori per creare applicativi macOs e iOS<\/p>\n\n\n\n Questo processo \u00e8 per\u00f2 lento, complicato e prono ad errori umani. Ma Hey! Sembra che una nuova opportunit\u00e0 si stagli davanti a noi! Quale caso d’uso migliore delle nuove istanze AWS EC2 Mac per creare un binario macOS?<\/p>\n\n\n\n E’ tempo di focalizzarci su come preparare il nostro banco di prova. Nelle prossime pagine andremo ad analizzare i seguenti passaggi:<\/p>\n\n\n\n La prima cosa che dobbiamo fare per configurare la nostra pipeline consiste nel creare l’istanza EC2 MAC su cui andremo a installare e configurare Jenkins.<\/p>\n\n\n\n Buttiamoci sulla console AWS per cominciare!<\/p>\n\n\n\n Come per ogni altra istanza EC2, il wizard di lancio di mac1.metal parte dalla console di EC2, alla voce “Istanze”. Come molti di voi sicuramente sanno, qui \u00e8 possibile trovare una lista di istanze EC2 disponibili e \u2014 tra le altre cose \u2014 il men\u00f9 necessario a lanciare questa nuova istanza.<\/p>\n\n\n\n Dal 30 Novembre 2020, la lista di AMI disponibili si \u00e8 arricchita infatti, ci mostra sia Mojave che Catalina, per i nostri scopi, scegliamo Catalina.<\/p>\n\n\n\n Per permettere l’installazione di un peso massimo come XCode \u2014 necessario per alcuni importantissimi strumenti di build \u2014 bisogna attaccare all’istanza un volume EBS di root con una dimensione appropriata; nel mio caso, ho scelto di agganciare un volume da 100GiB.<\/p>\n\n\n\n Prima di dedicarci ai tipi di accesso delle istanze Mac, ricordiamoci di configurare i Security Group attivando le regole che permettono l’accesso alla macchina via SSH e VNC. Inoltre ho configurato una regola per l’accesso alla macchina via browser tramite porta 8080, per intenderci, quella legata al servizio di Jenkins. Siccome ho deciso di lanciare l’istanza in una subnet pubblica, ho ristretto l’accesso soltanto al mio IP corrente.<\/p>\n\n\n\n NOTA: funziona, ma per favore tenete conto che il setup da me proposto \u2014 in termini di rete e sicurezza \u2014 non pu\u00f2 essere considerato production-ready! Per rendere il tutto pi\u00f9 sicuro, \u00e8 sufficiente spostare l’istanza Mac in una subnet privata, collegata ad un server openVPN in una subnet pubblica; in questo modo, l’istanza mac1.metal non \u00e8 pi\u00f9 esposta direttamente su internet, accedibile invece tramite il server OpenVPN.<\/p>\n\n\n\n Nell’ultimo passaggio del wizard di creazione dell’istanza, dobbiamo decidere se utilizzare una chiave .pem esistente o selezionarne una nuova per accedere alla macchina via SSH. Questo \u00e8 il primo tipo di accesso disponibile e, non \u00e8 per niente differente da un accesso SSH standard ad una istanza Linux; infatti, il nome utente per le macchine Mac \u00e8 proprio ec2-user.<\/p>\n\n\n\n Ma aspettate \u2014 come ci mostra l’ AWS Technical Evangelist S\u00e9bastien Stormacq in questo<\/a> video \u2014 dobbiamo configurare una cosa molto importante prima di poter accedere all’istanza. In particolare, dobbiamo scegliere una password per l’utente ec2-user ed abilitare il server VNC. Questo \u00e8 il Gist di Github che ho seguito e che si \u00e8 dimostrato indispensabile per me:<\/p>\n\n\n\n NOTA: le linee 45-49 sono fondamentali nel senso che, senza di esse, non sareste in grado di installare Xcode; la dimensione iniziale del container APFS non \u00e8 grande abbastanza per contenere Xcode.<\/p>\n\n\n\n E questo \u00e8 tutto per quello che riguarda i tipi di accesso.<\/p>\n\n\n\n La pipeline di build che andremo a configurare necessita di alcuni step preliminari che dovremo soddisfare. Ok, ma quali sono?<\/p>\n\n\n\n Quando possibile, ho preferito utilizzare Brew per l’installazione dei pacchetti. In particolare, l’ho utilizzato per installare Java e Jenkins; quindi, ho provveduto ad installare Xcode dall’ App Store, utilizzando le mie credenziali Apple.<\/p>\n\n\n\n Prima di tutto Xcode, il processo \u00e8 semplice: apriamo App Store, cerchiamo Xcode e lo installiamo.<\/p>\n\n\n\n Ora \u00e8 il turno di Java. Ho optato per Java AdoptOpenJDK versione 11, che pu\u00f2 essere installato tramite Brew in questo modo:<\/p>\n\n\n\n Per quanto riguarda Jenkins, invece, ho utilizzato il seguente comando:<\/p>\n\n\n\n Una volta installato, si pu\u00f2 rendere Jenkins accessibile dall’esterno modificando il file \/usr\/local\/opt\/jenkins-lts\/homebrew.mxcl.jenkins-lts.plist. In particolare, dobbiamo cambiare l’opzione –httpListenAddress da 127.0.0.1 a 0.0.0.0. Infine, lanciamo semplicemente il servizio con questo comando:<\/p>\n\n\n\n La configurazione iniziale di Jenkins prevede 3 passaggi importanti:<\/p>\n\n\n\n Prima di concentrarci sul processo di build, concentriamoci sul generare tutte le credenziali e i segreti necessari a scaricare il codice dal repository, firmare il binario, autenticarlo, e infine “pushare” l’artifact sul repository GitHub come nuova release draft.<\/p>\n\n\n\n Quindi, quali credenziali e segreti ci servono per questo processo di build?<\/p>\n\n\n\n Personalmente li ho settati come segreti globali a partire da <jenkins-url>\/credentials\/store\/system\/. Da qui, possiamo aggiungere credenziali e segreti rispettando i seguenti tipi:<\/p>\n\n\n\n Il segreto GitHub personal access token 2<\/strong> \u00e8 differente dal primo, nel senso che viene utilizzato per inviare l’artifact verso il repository GitHub, mentre il primo per ottenere il codice dal repository.<\/p>\n\n\n\n Possiamo lanciare il wizard di configurazione del processo di build premendo “New Item” dalla barra laterale a sinistra della Dashboard. Per quanto concerne il wizard, vedremo nel dettaglio le sezioni Source Code Management, Build Triggers, Build Environment Bindings, e Build.<\/p>\n\n\n\n Nella sezione Source Code Management, dobbiamo specificare le coordinate del nostro repository GitHub, aggiungere le credenziali di accesso, l’URL del repository, e il ramo dal quale Jenkins recuperer\u00e0 il codice.<\/p>\n\n\n\n A parte gli step 3 e 4, potreste chiedervi cosa sono gli script add-osx-cert.sh e dist-mac-prod. Perci\u00f2, voglio mostrarvi la loro implementazione.<\/p>\n\n\n\n Sempre per rimanere focalizzati, la parte importante da sapere nello script dist-mac-prod \u00e8<\/p>\n\n\n\n che permette di lanciare la build del binario.<\/p>\n\n\n\n Dentro il file package.json, c’\u00e8 un’altra parte molto importante che va specificata, ed \u00e8 quella inclusa nella sezione “build”; eccolo mostrato qui di seguito.<\/p>\n\n\n\n Come potete vedere, sto richiedendo di inviare l’artifact al repository ericvilla\/leapp \u2014 un fork di https:\/\/github.com\/Noovolari\/leapp<\/a> \u2014 come nuova release in DRAFT.<\/p>\n\n\n\n In pi\u00f9, ho specificato di lanciare lo script notarize.js subito dopo la firma. Questo script \u00e8 responsabile per l’autenticazione dell’applicazione macOS, ed \u00e8 implementato come segue:<\/p>\n\n\n\n Dipende dal pacchetto electron-notarize e cerca la variabile d’ambiente APPLE_NOTARISATION_PASSWORD; se non presente, la libreria assume che stiamo eseguendo la build sulla nostra macchina locale. Inoltre, ricerca la chiave appleIdPassword all’interno del Keychain di sistema.<\/p>\n\n\n\n Infine ma non meno importante, ho dovuto installare una versione di Node.js da dentro Jenkins per permettere alla soluzione di funzionare correttamente. Per fare questo, andiamo alla sezione “Manage Jenkins”, accessibile dal men\u00f9 laterale di sinistra. Una volta l\u00ec, premiamo “Global Tool Configuration”; quindi installiamo la versione di Node.js che ci serve. Nel mio caso, ho scelto la versione 12.9.1.<\/p>\n\n\n\n Se il servizio di Jenkins \u00e8 funzionante e il processo di Build \u00e8 configurato correttamente, nuovi push sul repository GitHub scateneranno la Build.<\/p>\n\n\n\n
Non preoccupatevi, entreremo nel dettaglio di uno di questi scenari a breve nell’articolo, ma per ora, permettetemi di introdurvi questo nuovo tipo di Istanza.
Le istanze Amazon EC2 Mac hanno un nome che richiama gli anni 80, un fatto questo, che le rende sicuramente “attraenti” per quelli di voi un p\u00f2 pi\u00f9 anziani di me ?.
Il loro nome \u00e8 istanze mac1.metal<\/strong>.
Parlando invece di hardware, le Istanze Amazon EC2 Mac sono supportate da host di tipo Mac mini, dipendenti da controller AWS Nitro per connettersi ai servizi e all’infrastruttura di rete di AWS. Il punto interessante \u00e8 che le istanze Mac sono interfacciate al sistema Nitro mediante tecnologia Thunderbolt 3. Ho volutamente utilizzato il termine “host” per sottolineare come non stiamo avendo a che fare con Macchine Virtuali, ma con veri e propri Host Dedicati; ogniqualvolta decido di avviare una Istanza Amazon EC2 Mac, AWS mi mette a disposizione concretamente un Host Mac mini dedicato ai miei scopi.<\/p>\n\n\n\nmac1.metal \u2014 uno sguardo alle specifiche<\/h2>\n\n\n\n
Nel pratico, un caso d’uso<\/h3>\n\n\n\n
\n
Lanciamo una istanza mac1.metal<\/h3>\n\n\n\n
<\/figure>\n\n\n\n
Nel momento in ci ci si trova a dover scegliere il tipo di istanza, solo una \u00e8 disponibile al momento, chiamata mac1-metal. Come gi\u00e0 sappiamo \u00e8 caratterizzata da 12 vCPU a 3.2GHz e 32GiB di memoria. Se la vediamo come se fosse la nostra macchina di lavoro, non suona cos\u00ec strano alla fine!<\/p>\n\n\n\n<\/figure>\n\n\n\n
Nel resto del wizard, possiamo trattare la nostra istanza Mac come ogni altra istanza; l’unica cosa su cui dovremmo soffermarci \u00e8 la voce “tenancy”. Dobbiamo lanciare l’istanza su una Macchina Dedicata che possiamo richiedere \u2014 in una tab separata \u2014 durante il processo di creazione dell’istanza. Anche per la Macchina Dedicata, dovremo specificare mac1.metal come tipo di istanza da poterci lanciare sopra.<\/p>\n\n\n\n<\/figure>\n\n\n\n
Per questa proof-of-concept, ho deciso di lanciare l’istanza in una subnet pubblica e abilitare l’auto assegnazione dell’IP.<\/p>\n\n\n\nE per quanto riguarda i tipi di accesso?<\/h3>\n\n\n\n
<\/figure>\n\n\n\n
Se avete bisogno di un accesso con interfaccia grafica all’istanza \u2014 necessario quando andremo ad installare i pacchetti software e i tool \u2014 potete utilizzare VNC; se siete utenti macOS, potete sfruttare VNC Viewer per configurare una connessione con l’istanza mac1.metal.<\/p>\n\n\n\n\n
1<\/td> # YouTube (english) : https:\/\/www.youtube.com\/watch?v=FtU2_bBfSgM<\/td><\/tr>\n 2<\/td> # YouTube (french) : https:\/\/www.youtube.com\/watch?v=VjnaVBnERDU<\/td><\/tr>\n 3<\/td> <\/td><\/tr>\n 4<\/td> #<\/td><\/tr>\n 5<\/td> # On your laptop, connect to the Mac instance with SSH (similar to Linux instances)<\/td><\/tr>\n 6<\/td> #<\/td><\/tr>\n 7<\/td> ssh -i 8<\/td> <\/td><\/tr>\n 9<\/td> #<\/td><\/tr>\n 10<\/td> # On the Mac<\/td><\/tr>\n 11<\/td> #<\/td><\/tr>\n 12<\/td> <\/td><\/tr>\n 13<\/td> # Set a password for ec2-user<\/td><\/tr>\n 14<\/td> <\/td><\/tr>\n 15<\/td> sudo passwd ec2-user <\/td><\/tr>\n 16<\/td> <\/td><\/tr>\n 17<\/td> # Enable VNC Server (thanks arnib@amazon.com for the feedback and tests) <\/td><\/tr>\n 18<\/td> <\/td><\/tr>\n 19<\/td> sudo \/System\/Library\/CoreServices\/RemoteManagement\/ARDAgent.app\/Contents\/Resources\/kickstart \\<\/td><\/tr>\n 20<\/td> -activate -configure -access -on \\<\/td><\/tr>\n 21<\/td> -configure -allowAccessFor -specifiedUsers \\<\/td><\/tr>\n 22<\/td> -configure -users ec2-user \\<\/td><\/tr>\n 23<\/td> -configure -restart -agent -privs -all<\/td><\/tr>\n 24<\/td> <\/td><\/tr>\n 25<\/td> sudo \/System\/Library\/CoreServices\/RemoteManagement\/ARDAgent.app\/Contents\/Resources\/kickstart \\<\/td><\/tr>\n 26<\/td> -configure -access -on -privs -all -users ec2-user<\/td><\/tr>\n 27<\/td> <\/td><\/tr>\n 28<\/td> exit<\/td><\/tr>\n 29<\/td> <\/td><\/tr>\n 30<\/td> #<\/td><\/tr>\n 31<\/td> # On your laptop<\/td><\/tr>\n 32<\/td> # Create a SSH tunnel to VNC and connect from a vnc client using user ec2-user and the password you defined.<\/td><\/tr>\n 33<\/td> #<\/td><\/tr>\n 34<\/td> <\/td><\/tr>\n 35<\/td> ssh -L 5900:localhost:5900 -C -N -i 36<\/td> <\/td><\/tr>\n 37<\/td> # open another terminal <\/td><\/tr>\n 38<\/td> <\/td><\/tr>\n 39<\/td> open vnc:\/\/localhost <\/td><\/tr>\n 40<\/td> <\/td><\/tr>\n 41<\/td> #<\/td><\/tr>\n 42<\/td> # On the mac, resize the APFS container to match EBS volume size<\/td><\/tr>\n 43<\/td> #<\/td><\/tr>\n 44<\/td> <\/td><\/tr>\n 45<\/td> PDISK=$(diskutil list physical external | head -n1 | cut -d” ” -f1)<\/td><\/tr>\n 46<\/td> APFSCONT=$(diskutil list physical external | grep “Apple_APFS” | tr -s ” ” | cut -d” ” -f8)<\/td><\/tr>\n 47<\/td> sudo diskutil repairDisk $PDISK<\/td><\/tr>\n 48<\/td> # Accept the prompt with “y”, then paste this command<\/td><\/tr>\n 49<\/td> sudo diskutil apfs resizeContainer $APFSCONT 0<\/td><\/tr>\n 50<\/td> <\/td><\/tr>\n 51<\/td> <\/td><\/tr>\n 52<\/td> <\/td><\/tr>\n 53<\/td> <\/td><\/tr>\n 54<\/td> <\/td><\/tr>\n 55<\/td> <\/td><\/tr>\n <\/table>\n <\/div>\n <\/div>\n\n\n\n Installiamo pacchetti e tool<\/h3>\n\n\n\n
\n
# Update brew first of all\nbrew update\n\n# Add adoptopenjdk\/openjdk repository\nbrew tap adoptopenjdk\/openjdk\n\n# Install Java AdoptOpenJDK 11\nbrew install --cask adoptopenjdk11\n<\/pre>\n\n\n\n
brew install jenkins-lts<\/pre>\n\n\n\n
brew services start jenkins-lts<\/pre>\n\n\n\n
Configurazione iniziale di Jenkins<\/h3>\n\n\n\n
\n
Credenziali & Segreti<\/h3>\n\n\n\n
\n
<\/figure>\n\n\n\n
Configurazione del processo di Build<\/h3>\n\n\n\n
<\/figure>\n\n\n\n
ho deciso di impostare il timer di Jenkins per i nuovi commit ogni 5 minuti, in modalit\u00e0 polling; l’alternativa consiste nel settare in Jenkins un webhook invocato direttamente da GitHub, in modalit\u00e0 push.<\/p>\n\n\n\n<\/figure>\n\n\n\n
Prossimo passaggio: Variabili d’Ambiente, che ho descritto come Build Environment Bindings<\/strong>. Qui dobbiamo semplicemente assegnare le credenziali e i segreti precedentemente configurati alle variabili d’ambiente necessarie ai prossimi passaggi del processo di Build.<\/p>\n\n\n\n<\/figure>\n\n\n\n
Finalmente, aggiungiamo i passaggi di build! Ho pensato di dividerli in 5 fasi, di fatto 5 “Execute shell commands”, che ho voluto illustrarvi qui sotto.<\/p>\n\n\n\n# Step 1\nchmod +x .\/scripts\/add-osx-cert.sh\n\n# Step 2\n.\/scripts\/add-osx-cert.sh\n\n# Step 3\nnpm install\n\n# Step 4\nnpm run rebuild-keytar\n\n# Step 5\nnpm run dist-mac-prod<\/pre>\n\n\n\n
\n
#!\/usr\/bin\/env sh\n\nKEY_CHAIN=build.keychain\nCERTIFICATE_P12=certificate.p12\n\necho \"Recreate the certificate from the secure environment variable\"\necho $CERTIFICATE_OSX_P12 | base64 --decode > $CERTIFICATE_P12\n\necho \"security create-keychain\"\nsecurity create-keychain -p jenkins $KEY_CHAIN\necho \"security list-keychains\"\nsecurity list-keychains -s login.keychain build.keychain\necho \"security default-keychain\"\nsecurity default-keychain -s $KEY_CHAIN\necho \"security unlock-keychain\"\nsecurity unlock-keychain -p jenkins $KEY_CHAIN\necho \"security import\"\nsecurity import $CERTIFICATE_P12 -k $KEY_CHAIN -P \"\" -T \/usr\/bin\/codesign;\necho \"security find-identity\"\nsecurity find-identity -v\necho \"security set-key-partition-list\"\nsecurity set-key-partition-list -S apple-tool:,apple:,codesign:, -s -k jenkins $KEY_CHAIN\n\n# Remove certs\nrm -fr *.p12<\/pre>\n\n\n\n
\n
\"dist-mac-prod\": \"rm -rf electron\/dist && rm -rf release && rm -rf dist && tsc --p electron && ng build --aot --configuration production --base-href .\/ && mkdir -p electron\/dist\/electron\/assets\/images && cp -a electron\/assets\/images\/* electron\/dist\/electron\/assets\/images\/ && electron-builder build --publish=onTag\",<\/pre>\n\n\n\n
electron-builder build --publish=onTag<\/pre>\n\n\n\n
# ...\n\"build\": {\n \"publish\": [\n {\n \"provider\": \"github\",\n \"owner\": \"ericvilla\",\n \"repo\": \"leapp\",\n \"releaseType\": \"draft\"\n }\n ],\n \"afterSign\": \"scripts\/notarize.js\",\n# ...<\/pre>\n\n\n\n
const { notarize } = require('electron-notarize');\n\nexports.default = async function notarizing(context) {\n const { electronPlatformName, appOutDir } = context;\n if (electronPlatformName !== 'darwin') {\n return;\n }\n\n const appName = context.packager.appInfo.productFilename;\n\n return await notarize({\n appBundleId: 'com.noovolari.leapp',\n appPath: `${appOutDir}\/${appName}.app`,\n appleId: \"mobile@besharp.it\",\n appleIdPassword: process.env.APPLE_NOTARISATION_PASSWORD ? process.env.APPLE_NOTARISATION_PASSWORD :\n \"@keychain:Leapp\",\n });\n};<\/pre>\n\n\n\n
<\/figure>\n\n\n\n
Ed eccoci qui, alla fine della configurazione del processo di Build!<\/p>\n\n\n\n