Banatutako sistema baten konfigurazio konpilagarria

Post honetan sistema banatu baten konfigurazioari aurre egiteko modu interesgarri bat partekatu nahi dugu.
Konfigurazioa Scala hizkuntzan zuzenean adierazten da modu seguruan. Inplementazio adibide bat xehetasunez deskribatzen da. Proposamenaren hainbat alderdi eztabaidatzen dira, garapen prozesu orokorrean eragina barne.

Banatutako sistema baten konfigurazio konpilagarria

(errusieraz)

Sarrera

Banatutako sistema sendoak eraikitzeak nodo guztietan konfigurazio zuzena eta koherentea erabiltzea eskatzen du. Soluzio tipiko bat inplementazio testualaren deskribapena (terraform, ansible edo antzeko zerbait) eta automatikoki sortutako konfigurazio-fitxategiak erabiltzea da (askotan, nodo/rol bakoitzerako dedikatuta). Komunikazio nodo bakoitzean bertsio bereko protokolo berdinak ere erabili nahi genituzke (bestela bateraezintasun arazoak izango genituzke). JVM munduan horrek esan nahi du gutxienez mezularitza-liburutegiak bertsio berekoa izan behar duela komunikazio-nodo guztietan.

Zer gertatzen da sistema probatzea? Jakina, osagai guztien unitate-probak egin beharko genituzke integrazio-probetara heldu aurretik. Exekuzio-denboran proben emaitzak estrapolatu ahal izateko, ziurtatu behar dugu liburutegi guztien bertsioak berdin mantentzen direla bai exekuzio-denboran, bai proba-inguruneetan.

Integrazio probak exekutatzen direnean, askotan askoz errazagoa da nodo guztietan klase-bide bera izatea. Inplementazioan klase bide bera erabiltzen dela ziurtatu behar dugu. (Posible da klase-bide desberdinak erabiltzea nodo desberdinetan, baina zailagoa da konfigurazio hau irudikatzea eta behar bezala zabaltzea.) Beraz, gauzak sinpleak izan daitezen nodo guztietan klase-bide berdinak bakarrik hartuko ditugu kontuan.

Konfigurazioak softwarearekin batera eboluzionatu ohi du. Normalean bertsioak erabiltzen ditugu hainbat identifikatzeko
softwarearen bilakaeraren faseak. Arrazoizkoa dirudi bertsioen kudeaketan konfigurazioa estaltzea eta etiketa batzuekin konfigurazio desberdinak identifikatzea. Ekoizpenean konfigurazio bakarra badago, bertsio bakarra erabil dezakegu identifikatzaile gisa. Batzuetan ekoizpen-ingurune anitz izan ditzakegu. Eta ingurune bakoitzerako konfigurazio-adar bereizi bat beharko genuke. Beraz, konfigurazioak adarrekin eta bertsioarekin etiketatuta egon daitezke konfigurazio desberdinak modu berezian identifikatzeko. Adarren etiketa eta bertsio bakoitza nodo bakoitzean banatutako nodoen, ataken, kanpoko baliabideen, classpath liburutegien bertsioen konbinazio bakar bati dagokio. Hemen adar bakarra estaliko dugu eta konfigurazioak hiru osagai hamartarren bertsio baten bidez identifikatuko ditugu (1.2.3), beste artefaktu batzuen modu berean.

Ingurune modernoetan konfigurazio fitxategiak ez dira eskuz aldatzen. Normalean sortzen dugu
konfigurazio fitxategiak inplementazio garaian eta inoiz ukitu ondoren. Beraz, galdetu liteke zergatik erabiltzen dugu oraindik testu formatua konfigurazio fitxategietarako? Aukera bideragarria da konfigurazioa konpilazio-unitate baten barruan jartzea eta konpilazio garaiko konfigurazioaren baliozkotzeari etekina ematea.

Post honetan konfigurazioa konpilatutako artefaktuan mantentzeko ideia aztertuko dugu.

Konfigurazio konpilagarria

Atal honetan konfigurazio estatikoen adibide bat eztabaidatuko dugu. Bi zerbitzu sinple: echo zerbitzua eta echo zerbitzuaren bezeroa konfiguratzen eta inplementatzen ari dira. Ondoren, bi zerbitzuak dituzten bi sistema banatu desberdin instanziatzen dira. Bata nodo bakarreko konfiguraziorako da eta beste bat bi nodoen konfiguraziorako.

Banatutako sistema tipiko batek nodo batzuk ditu. Nodoak mota batzuk erabiliz identifikatu litezke:

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

edo besterik ez

case class NodeId(hostName: String)

edo are

object Singleton
type NodeId = Singleton.type

Nodo hauek hainbat rol betetzen dituzte, zerbitzu batzuk exekutatzen dituzte eta beste nodoekin TCP/HTTP konexioen bidez komunikatzeko gai izan behar dute.

TCP konexiorako gutxienez ataka-zenbaki bat behar da. Bezeroa eta zerbitzaria protokolo berean hitz egiten ari direla ziurtatu nahi dugu. Nodoen arteko konexio bat modelatzeko, deklara dezagun klase hau:

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

non Port bat besterik ez da Int baimendutako barrutian:

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

Mota finduak

Ikusi findu liburutegia. Laburbilduz, beste mota batzuei konpilazio denbora mugak gehitzeko aukera ematen du. Kasu honetan Int ataka-zenbakia irudika dezaketen 16 biteko balioak soilik izatea onartzen da. Ez dago liburutegi hau erabiltzeko baldintzarik konfigurazio ikuspegi honetarako. Oso ondo moldatzen dela dirudi.

HTTP (REST) ​​zerbitzuaren bide bat ere beharko genuke:

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

Fantasma mota

Konpilazioan protokoloa identifikatzeko Scala ezaugarria erabiltzen ari gara mota argumentua deklaratzeko Protocol klasean erabiltzen ez dena. Hala deitzen da fantasma mota. Exekuzioan oso gutxitan behar dugu protokoloaren identifikatzailearen instantzia bat, horregatik ez dugu gordetzen. Konpilazioan zehar mota fantasma honek segurtasun mota gehigarria ematen du. Ezin dugu portua pasa protokolo oker batekin.

Gehien erabiltzen den protokoloetako bat REST APIa da Json serializazioarekin:

sealed trait JsonHttpRestProtocol[RequestMessage, ResponseMessage]

non RequestMessage bezeroak zerbitzariari bidal ditzakeen oinarrizko mezu mota da eta ResponseMessage zerbitzariaren erantzun-mezua da. Jakina, komunikazio-protokoloa nahi den zehaztasunarekin zehazten duten beste protokolo deskribapen batzuk sor ditzakegu.

Post honen helburuetarako protokoloaren bertsio sinpleago bat erabiliko dugu:

sealed trait SimpleHttpGetRest[RequestMessage, ResponseMessage]

Protokolo honetan eskaera-mezua url-era gehitzen da eta erantzun-mezua kate arrunt gisa itzultzen da.

Zerbitzuaren konfigurazio bat zerbitzuaren izenak, ataka bilduma batek eta mendekotasun batzuekin deskriba liteke. Elementu horiek guztiak Scala-n irudikatzeko modu posible batzuk daude (adibidez, HList, datu aljebraikoak). Post honen helburuetarako Cake Pattern erabiliko dugu eta konbina daitezkeen piezak (moduluak) ezaugarri gisa irudikatuko ditugu. (Pastelaren eredua ez da konfigurazio-ikuspegi konpilagarri honetarako baldintza bat. Ideia inplementazio posible bat besterik ez da.)

Mendekotasunak beste nodo batzuen amaierako puntu gisa Cake Pattern erabiliz irudikatu litezke:

  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 zerbitzuak ataka bat bakarrik konfiguratu behar du. Eta ataka honek oihartzun protokoloa onartzen duela adierazten dugu. Kontuan izan momentu honetan ez dugula ataka jakin bat zehaztu behar, ezaugarriak metodo abstraktuak deklaratzeko aukera ematen duelako. Metodo abstraktuak erabiltzen baditugu, konpilatzaileak inplementazio bat eskatuko du konfigurazio instantzia batean. Hemen inplementazioa eman dugu (8081) eta balio lehenetsi gisa erabiliko da konfigurazio konkretu batean saltatzen badugu.

Oihartzun zerbitzuaren bezeroaren konfigurazioan mendekotasuna deklara dezakegu:

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

Mendekotasunak mota bera du echoService. Bereziki, protokolo bera eskatzen du. Horregatik, ziur egon gaitezke bi mendekotasun hauek konektatzen baditugu ondo funtzionatuko dutela.

Zerbitzuak ezartzea

Zerbitzu batek funtzio bat behar du abiarazteko eta ondo ixteko. (Zerbitzu bat ixteko gaitasuna funtsezkoa da probak egiteko.) Berriro ere, konfigurazio jakin baterako funtzio bat zehazteko aukera batzuk daude (adibidez, mota klaseak erabil genitzake). Post honetarako Cake Pattern berriro erabiliko dugu. Zerbitzu bat erabiliz irudikatu dezakegu cats.Resource dagoeneko hortxetzea eta baliabideen askapena eskaintzen duena. Baliabide bat eskuratzeko konfigurazio bat eta exekuzio-testuinguru bat eman beharko genuke. Beraz, zerbitzua abiarazteko funtzioak itxura hau izan dezake:

  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]
  }

non

  • Config β€” Zerbitzu abiarazle honek eskatzen duen konfigurazio mota
  • AddressResolver β€” beste nodo batzuen benetako helbideak lortzeko gaitasuna duen exekuzio-denborako objektu bat (jarrai ezazu irakurtzen xehetasunetarako).

beste motak datoz cats:

  • F[_] β€” efektu mota (Kasurik errazenean F[A] justua izan liteke () => A. Post honetan erabiliko dugu cats.IO.)
  • Reader[A,B] β€” funtzio baten sinonimoa da gutxi gorabehera A => B
  • cats.Resource β€” eskuratzeko eta askatzeko bideak ditu
  • Timer β€” lo egiteko/denbora neurtzeko aukera ematen du
  • ContextShift - analogikoa ExecutionContext
  • Applicative - Funtzioen bilgarria indarrean (ia monada bat) (azkenean beste zerbaitekin ordezkatu genezake)

Interfaze hau erabiliz zerbitzu batzuk inplementa ditzakegu. Adibidez, ezer egiten ez duen zerbitzu bat:

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

(Ikus Iturburu-kodea beste zerbitzu batzuen ezarpenetarako - oihartzun zerbitzua,
echo bezeroa bizitza osorako kontrolagailuak.)

Nodo bat zerbitzu batzuk exekutatzen dituen objektu bakarra da (baliabide-kate bat abiaraztea Cake Pattern-ek gaitzen du):

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

Kontuan izan nodoan nodo honek behar duen konfigurazio mota zehatza zehazten dugula. Konpilatzaileak ez digu utziko objektua (Cake) mota nahikorik gabe eraikitzen, zerbitzu-ezaugarri bakoitzak muga bat adierazten duelako. Config mota. Gainera, ezingo dugu nodoa abiarazi konfigurazio osoa eman gabe.

Nodo helbidearen ebazpena

Konexio bat ezartzeko benetako ostalari helbide bat behar dugu nodo bakoitzeko. Baliteke konfigurazioaren beste atal batzuk baino beranduago ezagutzea. Hori dela eta, nodoaren id eta benetako helbidearen arteko mapa bat emateko modu bat behar dugu. Kartografia hau funtzio bat da:

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

Funtzio hori ezartzeko modu posible batzuk daude.

  1. Inplementatu aurretik benetako helbideak ezagutzen baditugu, nodoen ostalarien instantziazioan zehar, orduan Scala kodea sor dezakegu benetako helbideekin eta eraikitzea exekutatu ondoren (konpilazio-denboraren egiaztapenak egiten dituena eta integrazio-proba multzoa exekutatzen duena). Kasu honetan gure mapa-funtzioa estatikoki ezagutzen da eta a bezalako zerbaitetara sinplifikatu daiteke Map[NodeId, NodeAddress].
  2. Batzuetan, nodoa benetan abiarazten den une batean bakarrik lortzen ditugu benetako helbideak, edo ez ditugu oraindik hasi ez diren nodoen helbideak. Kasu honetan, beste nodo guztien aurretik abiarazten den aurkikuntza-zerbitzu bat izan dezakegu eta nodo bakoitzak bere helbidea iragar dezake zerbitzu horretan eta menpekotasunetara harpidetu.
  3. Aldatu ahal badugu /etc/hosts, aurrez definitutako ostalari-izenak erabil ditzakegu (adibidez my-project-main-node echo-backend) eta lotu izen hau ip helbidearekin inplementazio garaian.

Argitalpen honetan ez ditugu kasu hauek xehetasun gehiagorekin lantzen. Izan ere, gure jostailu adibidean nodo guztiek IP helbide bera izango dute - 127.0.0.1.

Argitalpen honetan bi sistema banatuko diseinuak hartuko ditugu kontuan:

  1. Nodo bakarreko diseinua, non zerbitzu guztiak nodo bakarrean kokatzen diren.
  2. Bi nodoen diseinua, non zerbitzua eta bezeroa nodo ezberdinetan dauden.

A-rako konfigurazioa nodo bakarra diseinua honakoa da:

Nodo bakarreko konfigurazioa

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.
}

Hemen zerbitzariaren eta bezeroaren konfigurazioa hedatzen duen konfigurazio bakarra sortzen dugu. Gainera, normalean bezeroa eta zerbitzaria amaituko dituen bizi-zikloko kontroladore bat konfiguratzen dugu lifetime tartea igarotzen da.

Zerbitzuen inplementazio eta konfigurazio multzo bera erabil daiteke sistema baten diseinua bi nodo bereiziekin sortzeko. Sortzea besterik ez dugu falta bi nodo konfigurazio bereizi zerbitzu egokiekin:

Bi nodoen konfigurazioa

  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"
  }

Ikusi nola zehazten dugun mendekotasuna. Uneko nodoaren menpekotasun gisa aipatzen dugu beste nodoaren emandako zerbitzua. Mendekotasun mota egiaztatzen da protokoloa deskribatzen duen fantasma mota duelako. Eta exekuzioan nodoaren id zuzena izango dugu. Hau da proposatutako konfigurazio ikuspegiaren alderdi garrantzitsuetako bat. Portua behin bakarrik ezartzeko eta ataka zuzenari erreferentzia egiten ari garela ziurtatzeko aukera ematen digu.

Bi nodo inplementazioa

Konfigurazio honetarako zerbitzuen inplementazio berdinak erabiltzen ditugu. Ez dago batere aldaketarik. Hala ere, zerbitzu multzo desberdinak dituzten bi nodo inplementazio desberdin sortzen ditugu:

  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
  }

Lehen nodoak zerbitzaria inplementatzen du eta zerbitzariaren alboko konfigurazioa besterik ez du behar. Bigarren nodoak bezeroa inplementatzen du eta konfigurazioaren beste zati bat behar du. Bi nodoek bizitzako zehaztapen batzuk behar dituzte. Post-zerbitzuaren nodo honen helburuetarako, erabilera amai daitekeen bizitza infinitua izango du SIGTERM, echo bezeroa, berriz, konfiguratutako iraupen mugatuaren ondoren amaituko da. Ikusi hasierako aplikazioa Xehetasunak.

Garapen prozesu orokorra

Ikus dezagun nola aldatzen duen ikuspegi honek konfigurazioarekin lan egiteko modua.

Kode gisa konfigurazioa konpilatu egingo da eta artefaktu bat sortzen du. Arrazoizkoa dirudi konfigurazio-artefaktua beste kode-artefaktuetatik bereiztea. Askotan konfigurazio ugari izan ditzakegu kode-oinarri berean. Eta noski, hainbat konfigurazio adarren bertsio anitz izan ditzakegu. Konfigurazio batean liburutegien bertsio jakin batzuk hauta ditzakegu eta hau konstante mantenduko da konfigurazio hau zabaltzen dugunean.

Konfigurazio aldaketa kode aldaketa bihurtzen da. Beraz, kalitatea bermatzeko prozesu berberarekin estali behar da:

Ticket -> PR -> berrikuspena -> batu -> etengabeko integrazioa -> etengabeko hedapena

Planteamenduaren ondorio hauek daude:

  1. Konfigurazioa koherentea da sistema jakin baten instantziarako. Badirudi ez dagoela modurik nodoen artean konexio okerrik izateko.
  2. Ez da erraza konfigurazioa nodo bakarrean aldatzea. Arrazoigabea dirudi saioa hastea eta testu fitxategi batzuk aldatzea. Beraz, konfigurazio-noraeza posibleagoa da.
  3. Konfigurazio aldaketa txikiak ez dira errazak egitea.
  4. Konfigurazio-aldaketa gehienek garapen-prozesu bera jarraituko dute, eta berrikuspen batzuk gaindituko dituzte.

Produkzioaren konfiguraziorako biltegi bereizi bat behar al dugu? Produkzio-konfigurazioak informazio sentikorra izan dezake, jende askoren eskura ez eduki nahiko genukeena. Beraz, baliteke ekoizpen-konfigurazioa edukiko duen sarbide mugatua duen biltegi bereizi bat mantentzea. Konfigurazioa bi zatitan bana dezakegu: bata ekoizpen-parametro irekienak dituena eta bestea konfigurazioaren zati sekretua duena. Honek garatzaile gehienentzat parametro gehienetarako sarbidea ahalbidetuko luke, gauza sentikorretarako sarbidea mugatuz. Erraza da parametro balio lehenetsiekin tarteko ezaugarriak erabiliz.

Aldakuntzak

Ikus ditzagun proposatutako planteamenduaren alde onak eta txarrak beste konfigurazio-kudeaketako teknikekin alderatuta.

Lehenik eta behin, proposatutako konfigurazioari aurre egiteko moduaren alderdi ezberdinen alternatiba batzuk zerrendatuko ditugu:

  1. Testu-fitxategia xede-makinan.
  2. Gako-balioen biltegiratze zentralizatua (adibidez etcd/zookeeper).
  3. Prozesua berrabiarazi gabe birkonfiguratu/berrabiarazi daitezkeen azpiprozesuko osagaiak.
  4. Artefaktu eta bertsio-kontroletik kanpoko konfigurazioa.

Testu-fitxategiak nolabaiteko malgutasuna ematen du ad-hoc konponketei dagokienez. Sistema baten administratzaileak xede-nodoan saioa hasi, aldaketa bat egin eta zerbitzua berrabiarazi besterik ez du egin. Baliteke hau sistema handiagoentzat ez izatea hain ona. Aldaketaren atzean ez da arrastorik geratzen. Aldaketa ez du beste begi pare batek berrikusten. Zaila izan daiteke aldaketa zerk eragin duen jakitea. Ez da probatu. Banatutako sistemaren ikuspuntutik administratzaileak beste nodoren batean konfigurazioa eguneratzea ahaztu dezake.

(Btw, azkenean testu-konfigurazio-fitxategiak erabiltzen hasi beharra egongo balitz, berdina sor dezaketen analizatzailea + balioztatzailea bakarrik gehitu beharko dugu. Config idatzi eta nahikoa izango litzateke testu-konfigurazioak erabiltzen hasteko. Horrek ere erakusten du konpilazio garaiko konfigurazioaren konplexutasuna testuan oinarritutako konfigurazioen konplexutasuna baino apur bat txikiagoa dela, testuan oinarritutako bertsioan kode gehigarri bat behar dugulako.)

Gako-balioen biltegiratze zentralizatua mekanismo ona da aplikazioen meta-parametroak banatzeko. Hemen konfigurazio-balioak direla eta datuak besterik ez diren pentsatu behar dugu. Funtzio bat emanda C => A => B gutxitan aldatzen diren balioei deitu ohi diegu C "konfigurazioa", maiz aldatzen diren datuak A - datuak sartu besterik ez. Konfigurazioa datuak baino lehenago eman behar zaio funtzioari A. Ideia hori kontuan hartuta, esan dezakegu aldaketen maiztasuna espero dela konfigurazio datuak datuetatik bereizteko erabil daitekeena. Gainera, normalean datuak iturri batetik datoz (erabiltzailea) eta konfigurazioa beste iturri batetik dator (administratzailea). Hasierako prozesuaren ondoren alda daitezkeen parametroei aurre egiteak aplikazioaren konplexutasuna areagotzea dakar. Horrelako parametroetarako haien entrega-mekanismoa, analisia eta baliozkotzea kudeatu beharko dugu, balio okerrak maneiatzen. Horregatik, programaren konplexutasuna murrizteko, hobe genuke exekuzioan alda daitezkeen parametroen kopurua murriztea (edo guztiz ezabatzea ere).

Post honen ikuspegitik parametro estatiko eta dinamikoen arteko bereizketa egin beharko genuke. Zerbitzu-logikak exekuzio garaian parametro batzuen aldaketa arraroa eskatzen badu, parametro dinamikoak dei ditzakegu. Bestela, estatikoak dira eta proposatutako ikuspegia erabiliz konfigura daitezke. Birkonfigurazio dinamikorako beste ikuspegi batzuk beharko lirateke. Adibidez, sistemaren zatiak konfigurazio-parametro berriekin berrabiarazi daitezke sistema banatu baten prozesu bereiziak berrabiaraztearen antzera.
(Nire iritzi xumea exekuzio denboraren birkonfigurazioa saihestea da, sistemaren konplexutasuna areagotzen duelako.
Baliteke errazagoa izan liteke prozesuak berrabiarazteko sistema eragilearen laguntzan fidatzea. Hala ere, agian ez da beti posible izango.)

Batzuetan jendeak konfigurazio dinamikoa kontuan hartzen duen konfigurazio estatikoa erabiltzearen alderdi garrantzitsu bat (beste arrazoirik gabe) zerbitzuaren geldialdia da konfigurazio eguneratzean. Izan ere, konfigurazio estatikoan aldaketak egin behar baditugu, sistema berrabiarazi beharko dugu, balio berriak eraginkorrak izan daitezen. Geldialdietarako eskakizunak sistema desberdinen arabera aldatzen dira, beraz, baliteke horren kritikoa ez izatea. Kritikoa bada, sistema edozein berrabiarazi aurretik planifikatu behar dugu. Adibidez, inplementa genezake AWS ELB konexioa drainatzea. Eszenatoki honetan sistema berrabiarazi behar dugun bakoitzean, sistemaren instantzia berri bat abiarazten dugu paraleloan, eta gero ELBra aldatzen dugu, sistema zaharrari lehendik dauden konexioei zerbitzua ematen uzten dion bitartean.

Zer gertatzen da bertsiodun artefaktuaren barruan edo kanpoan mantentzea? Konfigurazioa artefaktu baten barruan mantentzeak esan nahi du kasu gehienetan konfigurazio honek beste artefaktuen kalitatea bermatzeko prozesu bera gainditu duela. Beraz, ziur egon liteke konfigurazioa kalitate onekoa eta fidagarria dela. Aitzitik, fitxategi bereizi batean konfigurazioak esan nahi du ez dagoela fitxategi horretan aldaketak nork eta zergatik egin dituenaren arrastorik. Garrantzitsua al da hori? Uste dugu ekoizpen-sistema gehienentzat hobe dela konfigurazio egonkorra eta kalitate handikoa izatea.

Artefaktuaren bertsioak noiz sortu den, zer balio dituen, zer funtzio gaitu/desgaitu eta konfigurazioan aldaketa bakoitza egiteaz arduratu den jakiteko aukera ematen du. Baliteke artefaktu baten barruan konfigurazioa mantentzeko ahalegina behar izatea eta diseinu-aukera bat da.

Alde onak eta txarrak

Hemen proposatzen den ikuspegiaren abantaila batzuk nabarmendu nahi ditugu eta desabantaila batzuk eztabaidatu.

Abantailak

Banatutako sistema oso baten konfigurazio konpilagarriaren ezaugarriak:

  1. Konfigurazioaren egiaztapen estatikoa. Honek konfiantza maila altua ematen du, konfigurazioa zuzena dela mota-murrizketak kontuan hartuta.
  2. Konfigurazio hizkuntza aberatsa. Normalean beste konfigurazio-ikuspegiak gehienez ordezkapen aldakor batera mugatzen dira.
    Scala erabiliz hizkuntza-funtzio ugari erabil daitezke konfigurazioa hobeto egiteko. Esate baterako, ezaugarriak erabil ditzakegu balio lehenetsiak emateko, objektuak esparru desberdinak ezartzeko, erreferentzia egin dezakegu vals behin bakarrik definitzen da kanpoko esparruan (DRY). Posible da sekuentzia literalak edo zenbait klaseren instantzien erabilera (Seq, Map, Eta abar).
  3. DSL. Scalak laguntza duina dauka DSL idazleentzat. Ezaugarri hauek erabil daitezke konfigurazio-lengoaia erosoagoa eta azken erabiltzaileentzat atseginagoa dena ezartzeko, azken konfigurazioa gutxienez domeinuko erabiltzaileek irakur dezaten.
  4. Osotasuna eta koherentzia nodoen artean. Banatutako sistema osoaren konfigurazioa leku bakarrean edukitzearen abantailetako bat balio guztiak behin zorrozki definitzen direla da eta gero berrerabiltzen direla behar ditugun leku guztietan. Era berean, idatzi ataka seguruen adierazpenak ziurtatu ahal diren konfigurazio zuzen guztietan sistemaren nodoek hizkuntza bera hitz egingo dutela. Nodoen artean menpekotasun esplizituak daude eta horrek zaila egiten du zerbitzu batzuk eskaintzea ahaztea.
  5. Aldaketen kalitate handia. Konfigurazio-aldaketak PR prozesu arruntetik pasatzeko ikuspegi orokorrak kalitate estandar altuak ezartzen ditu konfigurazioan ere.
  6. Aldibereko konfigurazio-aldaketak. Konfigurazioan aldaketak egiten ditugunean, inplementazio automatikoak nodo guztiak eguneratzen ari direla ziurtatzen du.
  7. Aplikazioaren sinplifikazioa. Aplikazioak ez du zertan konfigurazioa analizatu eta balioztatu eta konfigurazio-balio okerrak kudeatu behar. Horrek aplikazio orokorra errazten du. (Konplexutasuna handitzea konfigurazioan bertan dago, baina segurtasunarekiko truke kontziente bat da.) Nahiko erraza da konfigurazio arruntera itzultzea β€” falta diren piezak gehitu besterik ez dago. Errazagoa da konpilatutako konfigurazioarekin hastea eta pieza gehigarrien inplementazioa geroago atzeratzea.
  8. Bertsiodun konfigurazioa. Konfigurazio-aldaketek garapen-prozesu bera jarraitzen dutelako, ondorioz, bertsio bakarra duen artefaktu bat lortzen dugu. Behar izanez gero, konfigurazioa atzera aldatzeko aukera ematen digu. Duela urtebete erabili zen konfigurazio bat ere zabaldu dezakegu eta modu berean funtzionatuko du. Konfigurazio egonkorrak sistema banatuaren aurreikusgarritasuna eta fidagarritasuna hobetzen ditu. Konfigurazioa konpilazio garaian finkatzen da eta ezin da erraz manipulatu ekoizpen-sistema batean.
  9. Modulartasuna. Proposatutako esparrua modularra da eta moduluak hainbat modutan konbina daitezke
    konfigurazio desberdinak onartzen ditu (konfigurazioak/diseinuak). Bereziki, posible da eskala txikiko nodo bakarreko diseinua eta eskala handiko nodo anitzeko ezarpena izatea. Arrazoizkoa da ekoizpen diseinu anitz izatea.
  10. Probak. Proba egiteko zerbitzu simulatu bat inplementatu eta mendekotasun gisa erabil daiteke modu seguruan. Proba-diseinu ezberdin batzuk maketak ordezkatuta hainbat piezarekin mantendu litezke aldi berean.
  11. Integrazio probak. Batzuetan, sistema banatuetan zaila da integrazio-probak egitea. Banatutako sistema osoaren konfigurazio segurua idazteko deskribatutako ikuspegia erabiliz, banatutako zati guztiak zerbitzari bakarrean exekutatu ditzakegu modu kontrolagarrian. Erraza da egoera imitatzea
    zerbitzuren bat erabilgarri gelditzen denean.

Desabantailak

Konpilatutako konfigurazio-ikuspegia konfigurazio "normalaren" desberdina da eta beharbada ez da behar guztietara egokitzea. Hona hemen konpilatutako konfigurazioaren desabantaila batzuk:

  1. Konfigurazio estatikoa. Baliteke aplikazio guztietarako egokia ez izatea. Zenbait kasutan, ekoizpenean konfigurazioa azkar konpondu beharra dago segurtasun neurri guztiak alde batera utzita. Ikuspegi honek zailagoa egiten du. Konfigurazioan edozein aldaketa egin ondoren, konpilazioa eta birmoldaketa beharrezkoak dira. Hau da ezaugarria eta zama.
  2. Konfigurazioa sortzea. Automatizazio tresnaren batek konfigurazioa sortzen duenean hurbilketa honek ondorengo konpilazioa behar du (horrek huts egin dezake). Baliteke urrats gehigarri hau eraikuntza-sisteman integratzeko ahalegin gehigarria behar izatea.
  3. Instrumentuak. Testuan oinarritutako konfigurazioetan oinarritzen diren tresna ugari erabiltzen dira gaur egun. Haietako batzuk
    ez da aplikagarria izango konfigurazioa konpilatzen denean.
  4. Pentsamolde aldaketa bat behar da. Garatzaileek eta DevOps-ek testu-konfigurazio fitxategiak ezagutzen dituzte. Konfigurazioa konpilatzearen ideia arraroa iruditu daiteke.
  5. Konfigurazio konpilagarria sartu aurretik kalitate handiko softwarea garatzeko prozesu bat behar da.

Inplementatutako adibidearen muga batzuk daude:

  1. Nodoaren inplementazioak eskatzen ez duen konfigurazio gehigarria ematen badugu, konpilatzaileak ez digu lagunduko ez dagoen inplementazioa detektatzen. Erabiliz zuzendu liteke HList edo ADTak (kasu-klaseak) nodoen konfiguraziorako ezaugarrien eta Cake Pattern ordez.
  2. Boilerplate batzuk eman behar ditugu konfigurazio fitxategian: (package, import, object adierazpenak;
    override defbalio lehenetsiak dituzten parametroetarako). Baliteke hori partzialki DSL bat erabiliz konpontzea.
  3. Post honetan ez dugu antzeko nodoen multzoen birkonfigurazio dinamikoa lantzen.

Ondorioa

Post honetan konfigurazioa zuzenean iturburu-kodean modu seguruan adierazteko ideia eztabaidatu dugu. Planteamendua aplikazio askotan erabil liteke xml eta testuan oinarritutako beste konfigurazio batzuen ordez. Gure adibidea Scala-n inplementatu den arren, beste hizkuntza konpilagarri batzuetara ere itzul liteke (Kotlin, C#, Swift, etab.). Ikuspegi hau proiektu berri batean probatu liteke eta, ondo moldatzen ez bada, antzinako erara jo.

Jakina, konfigurazio konpilagarriak kalitate handiko garapen prozesua behar du. Horren truke, kalitate handiko konfigurazio sendoa emango duela agintzen du.

Planteamendu hau hainbat modutara heda daiteke:

  1. Makroak erabil litezke konfigurazioen baliozkotzea egiteko eta konpilazio garaian huts egin dezake negozio-logikako murriztapenen hutsegiteen kasuan.
  2. DSL bat inplementatu liteke konfigurazioa domeinua erabiltzeko modu egokian irudikatzeko.
  3. Baliabideen kudeaketa dinamikoa konfigurazio doikuntza automatikoekin. Adibidez, kluster-nodoen kopurua doitzen dugunean (1) nodoek apur bat aldatutako konfigurazioa lortzea nahi dugu; (2) kluster kudeatzailea nodo berriak jasotzeko.

Eskerrik asko

Eskerrak eman nahi nizkieke Andrey Saksonov, Pavel Popov, Anton Nehaev-i, argiago uzten lagundu didaten mezu honen zirriborroari buruzko iritzi inspiratzaileak emateagatik.

Iturria: www.habr.com