HajussĂŒsteemi kompileeritav konfiguratsioon

Selles postituses tahaksime jagada huvitavat viisi hajutatud sĂŒsteemi konfigureerimisega tegelemiseks.
Konfiguratsioon on esitatud otse Scala keeles tĂŒĂŒbikindlal viisil. Rakenduse nĂ€idet kirjeldatakse ĂŒksikasjalikult. Arutatakse ettepaneku erinevaid aspekte, sealhulgas mĂ”ju ĂŒldisele arendusprotsessile.

HajussĂŒsteemi kompileeritav konfiguratsioon

(ĐœĐ° руссĐșĐŸĐŒ)

Sissejuhatus

Tugevate hajutatud sĂŒsteemide loomine nĂ”uab kĂ”igis sĂ”lmedes Ă”ige ja sidusa konfiguratsiooni kasutamist. TĂŒĂŒpiline lahendus on kasutada tekstilist juurutuse kirjeldust (terraform, ansible vĂ”i midagi sarnast) ja automaatselt genereeritud konfiguratsioonifaile (sageli iga sĂ”lme/rolli jaoks pĂŒhendatud). Samuti sooviksime kasutada igas suhtlussĂ”lmes samade versioonide samu protokolle (muidu tekiks kokkusobimatuse probleeme). JVM-i maailmas tĂ€hendab see, et vĂ€hemalt sĂ”numiteek peaks kĂ”igis suhtlussĂ”lmedes olema sama versiooniga.

Aga sĂŒsteemi testimine? Loomulikult peaks meil enne integratsioonitestide juurde asumist olema kĂ”igi komponentide jaoks ĂŒhikutestid. Testitulemuste kĂ€itusajal ekstrapoleerimiseks peaksime tagama, et kĂ”igi teekide versioonid oleksid nii kĂ€itus- kui ka testimiskeskkondades identsed.

Integratsioonitestide kÀitamisel on sageli palju lihtsam, kui kÔigil sÔlmedel on sama klassitee. Peame lihtsalt veenduma, et juurutamisel kasutatakse sama klassiteed. (Erinevates sÔlmedes on vÔimalik kasutada erinevaid klassiteid, kuid seda konfiguratsiooni on keerulisem esitada ja Ôigesti juurutada.) Nii et asjade lihtsaks hoidmiseks kÀsitleme kÔigis sÔlmedes ainult identseid klassiteid.

Konfiguratsioon kipub arenema koos tarkvaraga. Tavaliselt kasutame erinevate tuvastamiseks versioone
tarkvara evolutsiooni etapid. Tundub mĂ”istlik katta konfiguratsioon versioonihalduse alla ja tuvastada erinevad konfiguratsioonid teatud siltidega. Kui tootmises on ainult ĂŒks konfiguratsioon, vĂ”ime kasutada identifikaatorina ĂŒhte versiooni. MĂ”nikord vĂ”ib meil olla mitu tootmiskeskkonda. Ja iga keskkonna jaoks vĂ”ib vaja minna eraldi konfiguratsiooniharu. Seega vĂ”idakse konfiguratsioonid mĂ€rgistada haru ja versiooniga, et eri konfiguratsioone unikaalselt tuvastada. Iga haru silt ja versioon vastab iga sĂ”lme hajutatud sĂ”lmede, portide, vĂ€liste ressursside ja klassitee teegi versioonide ĂŒhele kombinatsioonile. Siin kĂ€sitleme ainult ĂŒhte haru ja tuvastame konfiguratsioonid kolmekomponendilise kĂŒmnendversiooni jĂ€rgi (1.2.3), samamoodi nagu muud artefaktid.

Kaasaegsetes keskkondades konfiguratsioonifaile enam kÀsitsi ei muudeta. Tavaliselt genereerime
konfiguratsioonifailid juurutamise ajal ja Ă€rge kunagi puudutage neid pĂ€rast. Seega vĂ”iks kĂŒsida, miks me ikkagi kasutame konfiguratsioonifailide jaoks tekstivormingut? MĂ”istlik vĂ”imalus on paigutada konfiguratsioon kompileerimisĂŒksusesse ja saada kasu kompileerimisaja konfiguratsiooni valideerimisest.

Selles postituses uurime ideed sÀilitada konfiguratsioon koostatud artefaktis.

Kompileeritav konfiguratsioon

Selles jaotises kĂ€sitleme staatilise konfiguratsiooni nĂ€idet. Kaks lihtsat teenust - kajateenus ja kajateenuse klient on seadistamisel ja juurutamisel. SeejĂ€rel luuakse kaks erinevat hajutatud sĂŒsteemi mĂ”lema teenusega. Üks on mĂ”eldud ĂŒhe sĂ”lme konfigureerimiseks ja teine ​​​​kahe sĂ”lme konfigureerimiseks.

TĂŒĂŒpiline hajutatud sĂŒsteem koosneb mĂ”nest sĂ”lmest. SĂ”lme saab tuvastada mĂ”ne tĂŒĂŒbi abil:

sealed trait NodeId
case object Backend extends NodeId
case object Frontend extends NodeId

vÔi lihtsalt

case class NodeId(hostName: String)

vÔi isegi

object Singleton
type NodeId = Singleton.type

Need sĂ”lmed tĂ€idavad erinevaid rolle, kĂ€itavad mĂ”ningaid teenuseid ja peaksid suutma suhelda teiste sĂ”lmedega TCP/HTTP ĂŒhenduste kaudu.

TCP-ĂŒhenduse jaoks on vaja vĂ€hemalt pordi numbrit. Samuti tahame veenduda, et klient ja server rÀÀgivad samast protokollist. SĂ”lmedevahelise ĂŒhenduse modelleerimiseks deklareerime jĂ€rgmise klassi:

case class TcpEndPoint[Protocol](node: NodeId, port: Port[Protocol])

kus Port on lihtsalt Int lubatud vahemikus:

type PortNumber = Refined[Int, Closed[_0, W.`65535`.T]]

Rafineeritud tĂŒĂŒbid

nĂ€gema puhastatud raamatukogu. LĂŒhidalt, see vĂ”imaldab lisada kompileerimisaja piiranguid teistele tĂŒĂŒpidele. Sel juhul Int lubatud on ainult 16-bitised vÀÀrtused, mis vĂ”ivad tĂ€histada pordi numbrit. Selle konfiguratsioonimeetodi jaoks pole seda teeki vaja kasutada. Tundub, et see sobib lihtsalt vĂ€ga hĂ€sti.

HTTP (REST) ​​jaoks vĂ”ime vajada ka teenuse teed:

type UrlPathPrefix = Refined[String, MatchesRegex[W.`"[a-zA-Z_0-9/]*"`.T]]
case class PortWithPrefix[Protocol](portNumber: PortNumber, pathPrefix: UrlPathPrefix)

FantoomtĂŒĂŒp

Protokolli tuvastamiseks kompileerimise ajal kasutame Scala funktsiooni tĂŒĂŒbiargumendi deklareerimiseks Protocol mida klassis ei kasutata. See on nn fantoomtĂŒĂŒp. KĂ€itusajal vajame harva protokolli identifikaatori eksemplari, seetĂ”ttu me seda ei salvesta. Koostamise ajal annab see fantoomtĂŒĂŒp tĂ€iendavat tĂŒĂŒpi turvalisust. Me ei saa vale protokolliga porti lĂ€bida.

Üks enim kasutatavaid protokolle on REST API koos Json-serialiseerimisega:

sealed trait JsonHttpRestProtocol[RequestMessage, ResponseMessage]

kus RequestMessage on pĂ”hitĂŒĂŒp sĂ”numid, mida klient saab saata serverisse ja ResponseMessage on vastussĂ”num serverilt. Muidugi vĂ”ime luua muid protokollikirjeldusi, mis tĂ€psustavad sideprotokolli soovitud tĂ€psusega.

Selle postituse jaoks kasutame protokolli lihtsamat versiooni:

sealed trait SimpleHttpGetRest[RequestMessage, ResponseMessage]

Selles protokollis lisatakse pÀringusÔnum URL-ile ja vastusesÔnum tagastatakse tavalise stringina.

Teenuse konfiguratsiooni saab kirjeldada teenuse nime, portide kogumi ja mĂ”ne sĂ”ltuvusega. KĂ”igi nende elementide esitamiseks Scalas on mĂ”ned vĂ”imalused (nĂ€iteks HList, algebralised andmetĂŒĂŒbid). Selle postituse jaoks kasutame koogi mustrit ja esindame kombineeritavaid tĂŒkke (mooduleid) tunnustena. (Selle kompileeritava konfiguratsiooni lĂ€henemisviisi puhul ei ole koogi muster nĂ”utav. See on vaid ĂŒks idee vĂ”imalik teostus.)

SÔltuvusi saab esitada, kasutades kooki mustrit teiste sÔlmede lÔpp-punktidena:

  type EchoProtocol[A] = SimpleHttpGetRest[A, A]

  trait EchoConfig[A] extends ServiceConfig {
    def portNumber: PortNumber = 8081
    def echoPort: PortWithPrefix[EchoProtocol[A]] = PortWithPrefix[EchoProtocol[A]](portNumber, "echo")
    def echoService: HttpSimpleGetEndPoint[NodeId, EchoProtocol[A]] = providedSimpleService(echoPort)
  }

Echo teenus vajab ainult konfigureeritud porti. Ja me teatame, et see port toetab kajaprotokolli. Pange tÀhele, et me ei pea praegu konkreetset porti mÀÀrama, kuna tunnus vÔimaldab abstraktseid meetodeid deklareerida. Kui kasutame abstraktseid meetodeid, nÔuab kompilaator konfiguratsioonieksemplaris rakendamist. Siin oleme pakkunud rakenduse (8081) ja seda kasutatakse vaikevÀÀrtusena, kui jÀtame selle konkreetses konfiguratsioonis vahele.

Saame deklareerida sÔltuvuse kajateenuse kliendi konfiguratsioonis:

  trait EchoClientConfig[A] {
    def testMessage: String = "test"
    def pollInterval: FiniteDuration
    def echoServiceDependency: HttpSimpleGetEndPoint[_, EchoProtocol[A]]
  }

SĂ”ltuvusel on sama tĂŒĂŒp kui echoService. EelkĂ”ige nĂ”uab see sama protokolli. Seega vĂ”ime olla kindlad, et kui ĂŒhendame need kaks sĂ”ltuvust, töötavad need Ă”igesti.

Teenuste juurutamine

Teenus vajab kĂ€ivitamiseks ja sujuvaks sulgemiseks funktsiooni. (Teenuse sulgemise vĂ”imalus on testimise jaoks ĂŒlioluline.) JĂ€llegi on mĂ”ned vĂ”imalused sellise funktsiooni mÀÀramiseks antud konfiguratsiooni jaoks (nĂ€iteks vĂ”ime kasutada tĂŒĂŒbiklasse). Selle postituse jaoks kasutame taas koogimustrit. Saame esindada teenust kasutades cats.Resource mis juba pakub kahveldust ja ressursside vabastamist. Ressursi hankimiseks peaksime esitama konfiguratsiooni ja teatud kĂ€itusaja konteksti. Seega vĂ”ib teenuse kĂ€ivitamise funktsioon vĂ€lja nĂ€ha jĂ€rgmine:

  type ResourceReader[F[_], Config, A] = Reader[Config, Resource[F, A]]

  trait ServiceImpl[F[_]] {
    type Config
    def resource(
      implicit
      resolver: AddressResolver[F],
      timer: Timer[F],
      contextShift: ContextShift[F],
      ec: ExecutionContext,
      applicative: Applicative[F]
    ): ResourceReader[F, Config, Unit]
  }

kus

  • Config — konfiguratsiooni tĂŒĂŒp, mida see teenuse kĂ€ivitaja nĂ”uab
  • AddressResolver — kĂ€itusobjekt, millel on vĂ”imalus hankida teiste sĂ”lmede tegelikke aadresse (ĂŒksikasjade saamiseks lugege edasi).

teised tĂŒĂŒbid pĂ€rinevad cats:

  • F[_] - efekti tĂŒĂŒp (lihtsamal juhul F[A] vĂ”iks olla lihtsalt () => A. Selles postituses kasutame cats.IO.)
  • Reader[A,B] — on enam-vĂ€hem funktsiooni sĂŒnonĂŒĂŒm A => B
  • cats.Resource — omab omandamise ja vabastamise viise
  • Timer — vĂ”imaldab magada/aega mÔÔta
  • ContextShift - analoog ExecutionContext
  • Applicative — kehtivate funktsioonide ĂŒmbris (peaaegu monaad) (vĂ”ime selle lĂ”puks asendada millegi muuga)

Seda liidest kasutades saame rakendada mÔningaid teenuseid. NÀiteks teenus, mis ei tee midagi:

  trait ZeroServiceImpl[F[_]] extends ServiceImpl[F] {
    type Config <: Any
    def resource(...): ResourceReader[F, Config, Unit] =
      Reader(_ => Resource.pure[F, Unit](()))
  }

(Vt LĂ€htekood muude teenuste juurutamise jaoks — kajateenus,
kaja klient ja eluaegsed kontrollerid.)

SĂ”lm on ĂŒks objekt, mis kĂ€itab mĂ”nda teenust (ressursside ahela kĂ€ivitamise lubab Cake Pattern):

object SingleNodeImpl extends ZeroServiceImpl[IO]
  with EchoServiceService
  with EchoClientService
  with FiniteDurationLifecycleServiceImpl
{
  type Config = EchoConfig[String] with EchoClientConfig[String] with FiniteDurationLifecycleConfig
}

Pange tĂ€hele, et sĂ”lmes mÀÀrame tĂ€pse konfiguratsioonitĂŒĂŒbi, mida see sĂ”lm vajab. Kompilaator ei lase meil ehitada ebapiisava tĂŒĂŒbiga objekti (Cake), kuna iga teenusetunnus deklareerib piirangu Config tĂŒĂŒp. Samuti ei saa me sĂ”lme kĂ€ivitada ilma tĂ€ielikku konfiguratsiooni esitamata.

SÔlme aadressi eraldusvÔime

Ühenduse loomiseks vajame iga sĂ”lme jaoks tĂ”elist hostiaadressi. See vĂ”ib olla teada hiljem kui konfiguratsiooni muud osad. SeetĂ”ttu vajame viisi sĂ”lme ID ja selle tegeliku aadressi vahelise vastendamiseks. See kaardistamine on funktsioon:

case class NodeAddress[NodeId](host: Uri.Host)
trait AddressResolver[F[_]] {
  def resolve[NodeId](nodeId: NodeId): F[NodeAddress[NodeId]]
}

Sellise funktsiooni rakendamiseks on mitu vÔimalikku viisi.

  1. Kui me teame tegelikke aadresse enne juurutamist, sÔlme hostide kÀivitamise ajal, saame genereerida Scala koodi tegelike aadressidega ja kÀivitada pÀrast seda ehitust (mis kontrollib kompileerimise aega ja kÀivitab seejÀrel integratsioonitestide komplekti). Sel juhul on meie kaardistamisfunktsioon staatiliselt tuntud ja seda saab lihtsustada nÀiteks a Map[NodeId, NodeAddress].
  2. MÔnikord saame tegelikud aadressid alles hiljem, kui sÔlm on tegelikult kÀivitatud, vÔi meil pole veel kÀivitamata sÔlmede aadresse. Sel juhul vÔib meil olla avastamisteenus, mis kÀivitatakse enne kÔiki teisi sÔlme ja iga sÔlm vÔib selles teenuses oma aadressi reklaamida ja sÔltuvusi tellida.
  3. Kui saame muuta /etc/hosts, saame kasutada eelmÀÀratletud hostinimesid (nt my-project-main-node ja echo-backend) ja seostage see nimi juurutamise ajal IP-aadressiga.

Selles postituses me neid juhtumeid ĂŒksikasjalikumalt ei kĂ€sitle. Tegelikult on meie mĂ€nguasja nĂ€ites kĂ”igil sĂ”lmedel sama IP-aadress - 127.0.0.1.

Selles postituses kĂ€sitleme kahte hajutatud sĂŒsteemi paigutust:

  1. Ühe sĂ”lme paigutus, kus kĂ”ik teenused on paigutatud ĂŒhele sĂ”lmele.
  2. Kahe sÔlme paigutus, kus teenus ja klient asuvad erinevates sÔlmedes.

Konfiguratsioon a ĂŒks sĂ”lm paigutus on jĂ€rgmine:

Ühe sĂ”lme konfiguratsioon

object SingleNodeConfig extends EchoConfig[String] 
  with EchoClientConfig[String] with FiniteDurationLifecycleConfig
{
  case object Singleton // identifier of the single node 
  // configuration of server
  type NodeId = Singleton.type
  def nodeId = Singleton

  /** Type safe service port specification. */
  override def portNumber: PortNumber = 8088

  // configuration of client

  /** We'll use the service provided by the same host. */
  def echoServiceDependency = echoService

  override def testMessage: UrlPathElement = "hello"

  def pollInterval: FiniteDuration = 1.second

  // lifecycle controller configuration
  def lifetime: FiniteDuration = 10500.milliseconds // additional 0.5 seconds so that there are 10 requests, not 9.
}

Siin loome ĂŒhe konfiguratsiooni, mis laiendab nii serveri kui ka kliendi konfiguratsiooni. Samuti konfigureerime elutsĂŒkli kontrolleri, mis tavaliselt lĂ”petab kliendi ja serveri pĂ€rast seda lifetime intervall möödub.

Sama teenuse juurutuste ja konfiguratsioonide komplekti saab kasutada kahe eraldi sĂ”lmega sĂŒsteemi paigutuse loomiseks. Peame lihtsalt looma kaks eraldi sĂ”lme konfiguratsiooni vastavate teenustega:

Kahe sÔlme konfiguratsioon

  object NodeServerConfig extends EchoConfig[String] with SigTermLifecycleConfig
  {
    type NodeId = NodeIdImpl

    def nodeId = NodeServer

    override def portNumber: PortNumber = 8080
  }

  object NodeClientConfig extends EchoClientConfig[String] with FiniteDurationLifecycleConfig
  {
    // NB! dependency specification
    def echoServiceDependency = NodeServerConfig.echoService

    def pollInterval: FiniteDuration = 1.second

    def lifetime: FiniteDuration = 10500.milliseconds // additional 0.5 seconds so that there are 10 request, not 9.

    def testMessage: String = "dolly"
  }

Vaadake, kuidas me sĂ”ltuvust mÀÀrame. Mainime teise sĂ”lme pakutavat teenust praeguse sĂ”lme sĂ”ltuvusena. SĂ”ltuvustĂŒĂŒpi kontrollitakse, kuna see sisaldab protokolli kirjeldavat fantoomtĂŒĂŒpi. Ja kĂ€itusajal on meil Ă”ige sĂ”lme ID. See on kavandatud konfiguratsioonimeetodi ĂŒks olulisi aspekte. See annab meile vĂ”imaluse mÀÀrata port ainult ĂŒks kord ja veenduda, et viitame Ă”igele pordile.

Kahe sÔlme rakendamine

Selle konfiguratsiooni jaoks kasutame tĂ€pselt samu teenuste rakendusi. Ei mingeid muudatusi ĂŒldse. Loome aga kaks erinevat sĂ”lmerakendust, mis sisaldavad erinevaid teenuseid:

  object TwoJvmNodeServerImpl extends ZeroServiceImpl[IO] with EchoServiceService with SigIntLifecycleServiceImpl {
    type Config = EchoConfig[String] with SigTermLifecycleConfig
  }

  object TwoJvmNodeClientImpl extends ZeroServiceImpl[IO] with EchoClientService with FiniteDurationLifecycleServiceImpl {
    type Config = EchoClientConfig[String] with FiniteDurationLifecycleConfig
  }

Esimene sĂ”lm rakendab serverit ja see vajab ainult serveripoolset konfiguratsiooni. Teine sĂ”lm rakendab klienti ja vajab konfiguratsiooni teist osa. MĂ”lemad sĂ”lmed nĂ”uavad teatud eluea spetsifikatsiooni. Selle postiteenuse sĂ”lmel on lĂ”pmatu eluiga, mida saab kasutades lĂ”petada SIGTERM, samas kui kajaklient lĂ”peb pĂ€rast konfigureeritud piiratud kestust. Vaata starterirakendus ĂŒksikasjad.

Üldine arendusprotsess

Vaatame, kuidas see lÀhenemisviis muudab konfiguratsiooniga töötamise viisi.

Konfiguratsioon koodina kompileeritakse ja see loob artefakti. Tundub mÔistlik eraldada konfiguratsiooniartefaktid muudest koodiartefaktidest. Sageli vÔib meil olla palju konfiguratsioone samal koodibaasil. Ja loomulikult vÔib meil olla erinevatest konfiguratsiooniharudest mitu versiooni. Konfiguratsioonis saame valida konkreetsed teekide versioonid ja see jÀÀb konstantseks alati, kui seda konfiguratsiooni juurutame.

Konfiguratsioonimuudatusest saab koodimuutus. Seega peaks see olema hÔlmatud sama kvaliteedi tagamise protsessiga:

Pilet -> PR -> ĂŒlevaade -> ĂŒhendamine -> pidev integreerimine -> pidev kasutuselevĂ”tt

Sellel lÀhenemisel on jÀrgmised tagajÀrjed:

  1. Konfiguratsioon on konkreetse sĂŒsteemi eksemplari jaoks ĂŒhtne. Tundub, et sĂ”lmede vahel ei saa kuidagi valesti ĂŒhendust luua.
  2. Konfiguratsiooni muutmine ainult ĂŒhes sĂ”lmes pole lihtne. Tundub ebamĂ”istlik sisse logida ja mĂ”nda tekstifaili muuta. Seega muutub konfiguratsiooni triiv vĂ€hem vĂ”imalikuks.
  3. VĂ€ikseid konfiguratsioonimuudatusi pole lihtne teha.
  4. Enamik konfiguratsioonimuudatusi jĂ€rgib sama arendusprotsessi ja see lĂ€bib mĂ”ne ĂŒlevaatuse.

Kas vajame tootmise seadistamiseks eraldi hoidlat? Tootmiskonfiguratsioon vĂ”ib sisaldada tundlikku teavet, mida sooviksime hoida paljudele inimestele kĂ€ttesaamatus kohas. Seega tasub hoida eraldi piiratud juurdepÀÀsuga hoidlat, mis sisaldab tootmiskonfiguratsiooni. VĂ”ime konfiguratsiooni jagada kaheks osaks – ĂŒks, mis sisaldab tootmise kĂ”ige avatumaid parameetreid, ja teine, mis sisaldab konfiguratsiooni salajast osa. See vĂ”imaldaks enamikule arendajatest juurdepÀÀsu enamikule parameetritele, piirates samas juurdepÀÀsu tĂ”eliselt tundlikele asjadele. Seda on lihtne saavutada parameetrite vaikevÀÀrtustega vahepealsete tunnuste abil.

Variatsioonid

Vaatame pakutud lÀhenemisviisi plusse ja miinuseid vÔrreldes teiste konfiguratsioonihaldusmeetoditega.

KÔigepealt loetleme mÔned alternatiivid pakutud konfiguratsiooni kÀsitlemise viiside erinevatele aspektidele:

  1. Tekstifail sihtmasinas.
  2. Tsentraliseeritud vÔtmevÀÀrtuste salvestus (nt etcd/zookeeper).
  3. Alamprotsessi komponendid, mida saab ilma protsessi taaskĂ€ivitamata ĂŒmber konfigureerida/taaskĂ€ivitada.
  4. Konfiguratsioon vÀljaspool artefakti ja versioonikontrolli.

Tekstifail annab ad hoc paranduste osas mĂ”ningast paindlikkust. SĂŒsteemi administraator saab sihtsĂ”lme sisse logida, teha muudatusi ja lihtsalt teenuse taaskĂ€ivitada. Suuremate sĂŒsteemide jaoks ei pruugi see nii hea olla. Muudatusest ei jÀÀ jĂ€lgi. Muudatust ei vaata ĂŒle teine ​​silmapaar. VĂ”ib olla raske vĂ€lja selgitada, mis muutuse pĂ”hjustas. Seda ei ole testitud. Hajutatud sĂŒsteemi vaatenurgast vĂ”ib administraator lihtsalt unustada mĂ”ne muu sĂ”lme konfiguratsiooni vĂ€rskendamise.

(Btw, kui lĂ”puks on vaja hakata kasutama teksti konfiguratsioonifaile, peame lisama ainult parseri + validaatori, mis suudavad sama luua Config tĂŒĂŒp ja sellest piisaks tekstikonfiguratsioonide kasutamise alustamiseks. See nĂ€itab ka, et kompileerimisaja konfiguratsiooni keerukus on veidi vĂ€iksem kui tekstipĂ”histe konfiguratsioonide keerukus, kuna tekstipĂ”hises versioonis vajame lisakoodi.)

Tsentraliseeritud vĂ”tmevÀÀrtuste salvestus on hea mehhanism rakenduse metaparameetrite levitamiseks. Siin peame mĂ”tlema, mida peame konfiguratsioonivÀÀrtusteks ja mis on lihtsalt andmed. Antud funktsioon C => A => B nimetame tavaliselt harva muutuvaid vÀÀrtusi C "konfiguratsioon", samas kui andmeid muudetakse sageli A - lihtsalt sisestage andmed. Funktsiooni konfiguratsioon tuleks anda varem kui andmed A. Seda ideed arvestades vĂ”ime öelda, et konfiguratsiooniandmete eristamiseks lihtsalt andmetest saab kasutada eeldatavat muutuste sagedust. Samuti pĂ€rinevad andmed tavaliselt ĂŒhest allikast (kasutaja) ja konfiguratsioon teisest allikast (administraator). Parameetritega tegelemine, mida saab pĂ€rast initsialiseerimisprotsessi muuta, suurendab rakenduse keerukust. Selliste parameetrite puhul peame kĂ€sitlema nende edastamismehhanismi, sĂ”elumist ja valideerimist ning valede vÀÀrtuste kĂ€sitlemist. SeetĂ”ttu peaksime programmi keerukuse vĂ€hendamiseks vĂ€hendama parameetrite arvu, mis vĂ”ivad kĂ€itusajal muutuda (vĂ”i isegi kĂ”rvaldada need tĂ€ielikult).

Selle postituse vaatenurgast peaksime eristama staatilisi ja dĂŒnaamilisi parameetreid. Kui teenindusloogika nĂ”uab mĂ”ne parameetri harva muutmist kĂ€itusajal, siis vĂ”ime neid nimetada dĂŒnaamilisteks parameetriteks. Vastasel juhul on need staatilised ja neid saab kavandatud lĂ€henemisviisi abil konfigureerida. DĂŒnaamilise ĂŒmberkonfigureerimise jaoks vĂ”ib vaja minna teisi lĂ€henemisviise. NĂ€iteks vĂ”ib sĂŒsteemi osi taaskĂ€ivitada uute konfiguratsiooniparameetritega sarnaselt hajutatud sĂŒsteemi eraldi protsesside taaskĂ€ivitamisele.
(Minu tagasihoidlik arvamus on vĂ€ltida kĂ€itusaja ĂŒmberseadistamist, kuna see muudab sĂŒsteemi keerukamaks.
VÔib-olla oleks lihtsam protsesside taaskÀivitamisel lihtsalt OS-i toele loota. Kuigi see ei pruugi alati vÔimalik olla.)

Üks staatilise konfiguratsiooni kasutamise oluline aspekt, mis mĂ”nikord paneb inimesed (ilma muude pĂ”hjusteta) dĂŒnaamilist konfigureerimist kaaluma, on teenuse seisak konfiguratsiooni vĂ€rskendamise ajal. TĂ”epoolest, kui peame staatilist konfiguratsiooni muutma, peame sĂŒsteemi taaskĂ€ivitama, et uued vÀÀrtused muutuksid tĂ”husaks. NĂ”uded seisakuajale on erinevate sĂŒsteemide puhul erinevad, seega ei pruugi see olla nii kriitiline. Kui see on kriitiline, peame sĂŒsteemi taaskĂ€ivitamist ette planeerima. NĂ€iteks vĂ”iksime rakendada AWS ELB ĂŒhenduse tĂŒhjendamine. Selle stsenaariumi korral kĂ€ivitame iga kord, kui meil on vaja sĂŒsteemi taaskĂ€ivitada, paralleelselt sĂŒsteemi uue eksemplari, seejĂ€rel lĂŒlitame sellele ELB-i, laseme samal ajal vanal sĂŒsteemil olemasolevate ĂŒhenduste teenindamise lĂ”pule viia.

Aga konfiguratsiooni hoidmine versioonistatud artefakti sees vĂ”i vĂ€ljaspool? Konfiguratsiooni hoidmine artefakti sees tĂ€hendab enamikul juhtudel, et see konfiguratsioon on lĂ€binud sama kvaliteedi tagamise protsessi nagu teised artefaktid. Seega vĂ”ib olla kindel, et konfiguratsioon on kvaliteetne ja usaldusvÀÀrne. Vastupidi, konfiguratsioon eraldi failis tĂ€hendab, et pole jĂ€lgi selle kohta, kes ja miks selles failis muudatusi tegi. Kas see on oluline? Usume, et enamiku tootmissĂŒsteemide jaoks on parem stabiilne ja kvaliteetne konfiguratsioon.

Artefakti versioon vÔimaldab teada saada, millal see loodi, milliseid vÀÀrtusi see sisaldab, millised funktsioonid on lubatud/keelatud, kes vastutas iga konfiguratsioonimuudatuse tegemise eest. Konfiguratsiooni hoidmine artefakti sees vÔib nÔuda pingutusi ja see on disaini valik.

Plussid Miinused

Siinkohal tahaksime vÀlja tuua mÔned pakutud lÀhenemisviisi eelised ja arutada mÔningaid puudusi.

Eelised

TĂ€ieliku hajutatud sĂŒsteemi kompileeritava konfiguratsiooni omadused:

  1. Konfiguratsiooni staatiline kontroll. See annab kĂ”rge kindlustunde, et konfiguratsioon on tĂŒĂŒbipiiranguid arvestades Ă”ige.
  2. Rikkalik konfiguratsioonikeel. Tavaliselt piirduvad muud konfiguratsioonimeetodid maksimaalselt muutuva asendusega.
    Scalat kasutades saab konfiguratsiooni paremaks muutmiseks kasutada laias valikus keelefunktsioone. NĂ€iteks vĂ”ime kasutada tunnuseid vaikevÀÀrtuste pakkumiseks, objekte erineva ulatuse mÀÀramiseks, millele saame viidata vals on defineeritud ainult ĂŒks kord vĂ€lissfÀÀris (DRY). VĂ”imalik on kasutada sĂ”nasĂ”nalisi jadasid vĂ”i teatud klasside esinemisjuhte (Seq, MapJne).
  3. DSL. Scalal on korralik tugi DSL-i kirjutajatele. Nende funktsioonide abil saab luua mugavama ja lÔppkasutajasÔbralikuma konfiguratsioonikeele, nii et lÔplik konfiguratsioon on vÀhemalt domeeni kasutajatele loetav.
  4. SĂ”lmede terviklikkus ja sidusus. Üks kogu hajutatud sĂŒsteemi ĂŒhes kohas konfigureerimise eeliseid on see, et kĂ”ik vÀÀrtused mÀÀratletakse rangelt ĂŒks kord ja seejĂ€rel kasutatakse neid uuesti kĂ”igis kohtades, kus neid vajame. Sisestage ka turvalise pordi deklaratsioonid, mis tagavad, et sĂŒsteemi sĂ”lmed rÀÀgivad kĂ”igis vĂ”imalikes Ă”igetes konfiguratsioonides sama keelt. SĂ”lmede vahel on selged sĂ”ltuvused, mis muudab mĂ”ne teenuse pakkumise unustamise raskeks.
  5. Muudatuste kĂ”rge kvaliteet. Üldine lĂ€henemine konfiguratsioonimuutuste lĂ€bimisele tavapĂ€rase PR-protsessi kaudu kehtestab kĂ”rged kvaliteedistandardid ka konfiguratsioonis.
  6. Samaaegsed konfiguratsioonimuudatused. Kui teeme konfiguratsioonis muudatusi, tagab automaatne juurutamine, et kÔiki sÔlme vÀrskendatakse.
  7. Rakenduse lihtsustamine. Rakendus ei pea konfiguratsiooni sĂ”eluma ja kinnitama ega kĂ€sitlema valesid konfiguratsioonivÀÀrtusi. See lihtsustab ĂŒldist rakendust. (Teatud keerukus on konfiguratsioonis endas, kuid see on teadlik kompromiss ohutuse suunas.) Tavakonfiguratsiooni juurde naasmine on ĂŒsna lihtne – lihtsalt lisage puuduvad osad. Lihtsam on alustada kompileeritud konfiguratsiooniga ja lĂŒkata tĂ€iendavate osade juurutamine mĂ”nele hilisemale ajale.
  8. Versioonitud konfiguratsioon. Kuna konfiguratsioonimuudatused jĂ€rgivad sama arendusprotsessi, saame tulemuseks ainulaadse versiooniga artefakti. See vĂ”imaldab meil vajadusel konfiguratsiooni tagasi lĂŒlitada. Saame isegi juurutada konfiguratsiooni, mida kasutati aasta tagasi ja see töötab tĂ€pselt samamoodi. Stabiilne konfiguratsioon parandab hajutatud sĂŒsteemi prognoositavust ja töökindlust. Konfiguratsioon on kompileerimise ajal fikseeritud ja seda ei saa tootmissĂŒsteemis kergesti rikkuda.
  9. Modulaarsus. Kavandatav raamistik on modulaarne ja mooduleid saab kombineerida mitmel viisil
    toetada erinevaid konfiguratsioone (seadistusi/paigutusi). EelkĂ”ige on vĂ”imalik kasutada vĂ€ikesemahulist ĂŒhe sĂ”lme paigutust ja suuremahulist mitme sĂ”lme seadistust. On mĂ”istlik kasutada mitut tootmispaigutust.
  10. Testimine. Testimise eesmĂ€rgil vĂ”ib rakendada nĂ€idisteenust ja kasutada seda sĂ”ltuvusena tĂŒĂŒbikindlal viisil. Samaaegselt saab sĂ€ilitada mĂ”nda erinevat testimispaigutust, kus erinevad osad on asendatud pilkadega.
  11. Integratsiooni testimine. MĂ”nikord on hajutatud sĂŒsteemides integratsioonitestide kĂ€ivitamine keeruline. Kasutades kirjeldatud lĂ€henemisviisi tervikliku hajutatud sĂŒsteemi turvalise konfiguratsiooni tippimiseks, saame juhtida kĂ”iki hajutatud osi ĂŒhes serveris juhitaval viisil. Olukorda on lihtne jĂ€ljendada
    kui ĂŒks teenustest muutub kĂ€ttesaamatuks.

Puudused

Koostatud konfiguratsiooni lÀhenemisviis erineb "tavalisest" konfiguratsioonist ja see ei pruugi vastata kÔikidele vajadustele. Siin on mÔned koostatud konfiguratsiooni puudused:

  1. Staatiline konfiguratsioon. See ei pruugi kĂ”igi rakenduste jaoks sobida. MĂ”nel juhul on konfiguratsioon tootmises vaja kiiresti fikseerida, jĂ€ttes kĂ”rvale kĂ”ik ohutusmeetmed. See lĂ€henemine muudab selle keerulisemaks. Kompileerimine ja ĂŒmberpaigutamine on vajalik pĂ€rast konfiguratsiooni muutmist. See on nii omadus kui ka koormus.
  2. Konfiguratsiooni genereerimine. Kui konfiguratsiooni genereerib mĂ”ni automatiseerimistööriist, nĂ”uab see lĂ€henemine hilisemat kompileerimist (mis vĂ”ib omakorda ebaĂ”nnestuda). Selle tĂ€iendava sammu integreerimine ehitussĂŒsteemi vĂ”ib nĂ”uda tĂ€iendavaid jĂ”upingutusi.
  3. Instrumendid. TÀnapÀeval on kasutusel palju tööriistu, mis pÔhinevad tekstipÔhistel konfiguratsioonidel. MÔned neist
    ei kehti konfiguratsiooni koostamisel.
  4. Vaja on mÔtteviisi muutust. Arendajad ja DevOps tunnevad tekstikonfiguratsioonifaile. Konfiguratsiooni koostamise idee vÔib neile tunduda kummaline.
  5. Enne kompileeritava konfiguratsiooni kasutuselevÔttu on vajalik kvaliteetne tarkvara arendusprotsess.

Rakendatud nÀitel on mÔned piirangud:

  1. Kui pakume lisakonfiguratsiooni, mida sÔlme rakendamine ei nÔua, ei aita kompilaator meil puuduvat rakendust tuvastada. Seda saab lahendada kasutades HList vÔi ADT-d (juhtumiklassid) sÔlmede konfigureerimiseks tunnuste ja koogimustri asemel.
  2. Peame konfiguratsioonifailis esitama mÔne katlaplaadi: (package, import, object deklaratsioonid;
    override def's parameetrite jaoks, millel on vaikevÀÀrtused). Seda vÔib osaliselt lahendada DSL-i abil.
  3. Selles postituses me ei kĂ€sitle sarnaste sĂ”lmede klastrite dĂŒnaamilist ĂŒmberkonfigureerimist.

JĂ€reldus

Selles postituses oleme arutanud ideed esitada konfiguratsioon otse lĂ€htekoodis tĂŒĂŒbikindlal viisil. Seda lĂ€henemisviisi saab kasutada paljudes rakendustes xml- ja muude tekstipĂ”histe konfiguratsioonide asendajana. Vaatamata sellele, et meie nĂ€idet on Scalas rakendatud, saab seda tĂ”lkida ka teistesse kompileeritavatesse keeltesse (nt Kotlin, C#, Swift jne). Seda lĂ€henemist vĂ”iks proovida mĂ”nes uues projektis ja kui see hĂ€sti ei sobi, minna ĂŒle vanamoodsale.

Loomulikult nÔuab kompileeritav konfiguratsioon kvaliteetset arendusprotsessi. Vastutasuks lubab see pakkuda sama kvaliteetset ja tugevat konfiguratsiooni.

Seda lÀhenemisviisi saab laiendada mitmel viisil:

  1. Makrosid saab kasutada konfiguratsiooni valideerimiseks ja Àriloogika piirangute tÔrgete korral kompileerimise ajal ebaÔnnestuda.
  2. DSL-i saab rakendada konfiguratsiooni esitamiseks domeeni kasutajasÔbralikul viisil.
  3. DĂŒnaamiline ressursside haldamine koos automaatsete konfiguratsiooni kohandamisega. NĂ€iteks kui kohandame klastri sĂ”lmede arvu, vĂ”iksime soovida, et (1) sĂ”lmed saaksid veidi muudetud konfiguratsiooni; (2) klastrihaldur uute sĂ”lmede teabe saamiseks.

tÀnan

Tahaksin tÀnada Andrei Saksonovit, Pavel Popovit ja Anton Nehajevit inspireeriva tagasiside eest selle postituse mustandile, mis aitas mul seda selgemaks teha.

Allikas: www.habr.com

Ostke DDoS-kaitsega saitide jaoks usaldusvÀÀrne hostimine, VPS VDS-serverid đŸ”„ Osta usaldusvÀÀrne veebimajutus DDoS-kaitsega, VPS VDS serverid | ProHoster