Sistema banatuaren konfigurazioa konpilatua

Banatutako sistema baten konfigurazioarekin lan egiteko mekanismo interesgarri bat esan nahiko nuke. Konfigurazioa zuzenean konpilatutako hizkuntza batean (Scala) adierazten da mota seguruak erabiliz. Argitalpen honek konfigurazio horren adibide bat eskaintzen du eta garapen prozesu orokorrean konpilatutako konfigurazio bat ezartzeko hainbat alderdi aztertzen ditu.

Sistema banatuaren konfigurazioa konpilatua

(ingelesa)

Sarrera

Banatutako sistema fidagarri bat eraikitzeak esan nahi du nodo guztiek konfigurazio zuzena erabiltzen dutela, beste nodo batzuekin sinkronizatuta. DevOps teknologiak (terraform, ansible edo antzeko zerbait) konfigurazio-fitxategiak automatikoki sortzeko erabili ohi dira (askotan nodo bakoitzeko espezifikoak). Era berean, ziurtatu nahi genuke komunikazio-nodo guztiek protokolo berdinak erabiltzen dituztela (bertsio bera barne). Bestela, bateraezintasuna sartuko da gure sistema banatuan. JVM munduan, eskakizun honen ondorio bat protokolo-mezuak dituen liburutegiaren bertsio bera erabili behar da nonahi.

Zer gertatzen da banatutako sistema bat probatzea? Jakina, osagai guztiek unitate-probak dituztela suposatzen dugu integrazio-probetara pasatu aurretik. (Proben emaitzak exekuzio-garaian estrapolatzeko, liburutegi-multzo berdina ere eman behar dugu proba-fasean eta exekuzioan).

Integrazio probekin lan egitean, askotan errazagoa da nodo guztietan klase-bide bera erabiltzea nodo guztietan. Egin behar duguna da exekuzioan klase bide bera erabiltzen dela ziurtatzea. (Guztiz posible den arren, nodo desberdinak klase-bide ezberdinekin exekutatu, horrek konplexutasuna gehitzen dio konfigurazio orokorrari eta hedapen eta integrazio-probetan zailtasunak).

Konfigurazioa aplikazioarekin batera eboluzionatzen da. Bertsioak erabiltzen ditugu programaren bilakaeraren etapa desberdinak identifikatzeko. Logikoa dirudi konfigurazioen bertsio desberdinak ere identifikatzea. Eta jarri konfigurazioa bera bertsio-kontrol sisteman. Ekoizpenean konfigurazio bakarra badago, bertsio zenbakia erabil dezakegu. Produkzio-instantzia asko erabiltzen baditugu, hainbat beharko ditugu
konfigurazio adarrak eta bertsioaz gain etiketa gehigarri bat (adibidez, adarraren izena). Horrela argi eta garbi identifikatu dezakegu konfigurazio zehatza. Konfigurazio-identifikatzaile bakoitza banatutako nodoen, ataken, kanpoko baliabideen eta liburutegien bertsioen konbinazio zehatz bati dagokio. Post honen ondorioetarako adar bakarra dagoela suposatuko dugu eta konfigurazioa ohiko moduan identifika dezakegula puntu batez bereizitako hiru zenbaki erabiliz (1.2.3).

Ingurune modernoetan, konfigurazio fitxategiak oso gutxitan sortzen dira eskuz. Gehiagotan inplementazioan sortzen dira eta jada ez dira ukitzen (beraz ez hautsi ezer). Galdera natural bat sortzen da: zergatik erabiltzen dugu oraindik testu formatua konfigurazioa gordetzeko? Badirudi alternatiba bideragarri bat konfiguraziorako kode arrunta erabiltzeko eta konpilazio garaiko egiaztapenei etekina ateratzea dela.

Post honetan konpilatutako artefaktu baten barruan konfigurazio bat irudikatzeko ideia aztertuko dugu.

Konpilatutako konfigurazioa

Atal honek konpilatutako konfigurazio estatiko baten adibidea eskaintzen du. Bi zerbitzu sinple inplementatzen dira: oihartzun zerbitzua eta oihartzun zerbitzuaren bezeroa. Bi zerbitzu horietan oinarrituta, bi sistema-aukera muntatzen dira. Aukera batean, bi zerbitzuak nodo berean daude, beste aukera batean - nodo desberdinetan.

Normalean sistema banatu batek hainbat nodo ditu. Nodoak identifikatu ditzakezu mota bateko balioak erabiliz NodeId:

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

edo

case class NodeId(hostName: String)

edo are

object Singleton
type NodeId = Singleton.type

Nodoek hainbat rol betetzen dituzte, zerbitzuak exekutatzen dituzte eta haien artean TCP/HTTP konexioak ezar daitezke.

TCP konexio bat deskribatzeko gutxienez ataka-zenbaki bat behar dugu. Era berean, ataka horretan onartzen den protokoloa islatu nahiko genuke, bezeroak eta zerbitzariak protokolo bera erabiltzen dutela ziurtatzeko. Konexioa deskribatuko dugu klase hau erabiliz:

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

non Port - zenbaki oso bat besterik ez Int balio onargarrien tartea adieraziz:

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

Mota finduak

Ikusi liburutegia findu ΠΈ nire txostena. Laburbilduz, liburutegiak konpilazio garaian egiaztatzen diren motei mugak gehitzeko aukera ematen dizu. Kasu honetan, baliozko ataka-zenbakien balioak 16 biteko zenbaki osoak dira. Konpilatutako konfigurazio baterako, liburutegi findua erabiltzea ez da derrigorrezkoa, baina konpilatzaileak konfigurazioa egiaztatzeko duen gaitasuna hobetzen du.

HTTP (REST) ​​protokoloetarako, ataka zenbakiaz gain, zerbitzurako bidea ere behar dugu:

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

Phantom motak

Konpilazio garaian protokoloa identifikatzeko, klasean erabiltzen ez den parametro mota bat erabiltzen dugu. Erabaki hau exekuzioan ez dugulako protokolo-instantziarik erabiltzen, baina konpilatzaileak protokoloaren bateragarritasuna egiaztatzea nahiko genuke. Protokoloa zehaztuta, ezingo dugu zerbitzu desegokirik pasa menpekotasun gisa.

Protokolo arruntetako bat Json serializazioa duen REST APIa da:

sealed trait JsonHttpRestProtocol[RequestMessage, ResponseMessage]

non RequestMessage - eskaera mota, ResponseMessage β€” Erantzun mota.
Jakina, behar dugun deskribapenaren zehaztasuna ematen duten beste protokolo deskribapen batzuk erabil ditzakegu.

Argitalpen honen helburuetarako, protokoloaren bertsio sinplifikatu bat erabiliko dugu:

sealed trait SimpleHttpGetRest[RequestMessage, ResponseMessage]

Hemen eskaera url-ari erantsitako kate bat da eta erantzuna HTTP erantzunaren gorputzean itzultzen den katea da.

Zerbitzuaren konfigurazioa zerbitzuaren izenak, atakak eta mendekotasunek deskribatzen dute. Elementu hauek Scala-n hainbat modutan irudika daitezke (adibidez, HList-s, datu aljebraikoak). Post honen helburuetarako, pastelaren eredua erabiliko dugu eta moduluak erabiliz irudikatuko dugu trait'ov. (Pastelaren eredua ez da ikuspegi honen beharrezko elementu bat. Inplementazio posible bat besterik ez da.)

Zerbitzuen arteko mendekotasunak portuak itzultzen dituzten metodo gisa irudika daitezke EndPointbeste nodo batzuenak:

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

Oihartzun-zerbitzu bat sortzeko, ataka-zenbaki bat eta portuak oihartzun-protokoloa onartzen duela adieraztea besterik ez da behar. Agian ez dugu ataka zehatz bat zehaztu, zeren... ezaugarriek metodoak inplementaziorik gabe deklaratzeko aukera ematen dute (metodo abstraktuak). Kasu honetan, konfigurazio konkretu bat sortzean, konpilatzaileak metodo abstraktuaren inplementazioa eskaintzea eta ataka-zenbakia ematea eskatuko liguke. Metodoa inplementatu dugunez, konfigurazio zehatz bat sortzerakoan, baliteke beste atakarik ez zehaztea. Balio lehenetsia erabiliko da.

Bezeroaren konfigurazioan oihartzun zerbitzuaren menpekotasuna adierazten dugu:

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

Mendekotasuna esportatutako zerbitzuaren mota berekoa da echoService. Bereziki, echo bezeroan protokolo bera eskatzen dugu. Hori dela eta, bi zerbitzu konektatzean, dena ondo funtzionatuko duela ziur egon gaitezke.

Zerbitzuak ezartzea

Funtzio bat behar da zerbitzua abiarazteko eta gelditzeko. (Zerbitzu bat geldiarazteko gaitasuna funtsezkoa da probak egiteko.) Berriz ere, hainbat aukera daude ezaugarri hori ezartzeko (adibidez, konfigurazio motaren araberako mota klaseak erabil genitzake). Post honen helburuetarako Tarta Eredua erabiliko dugu. Zerbitzua klase baten bidez irudikatuko dugu cats.Resource, zeren Klase honek arazoen kasuan baliabideen askapena segurtasunez bermatzeko baliabideak eskaintzen ditu. Baliabide bat lortzeko, konfigurazioa eta prest dagoen exekuzio-testuinguru bat eman behar dugu. Zerbitzua abiarazteko funtzioa honelakoa izan daiteke:

  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 honen konfigurazio mota
  • AddressResolver β€” beste nodo batzuen helbideak ezagutzeko aukera ematen duen exekuzio-objektu bat (ikus behean)

eta liburutegitik beste mota batzuk cats:

  • F[_] β€” efektu mota (kasurik errazenean F[A] funtzio bat izan liteke () => A. Post honetan erabiliko dugu cats.IO.)
  • Reader[A,B] - funtzioaren sinonimo gehiago edo gutxiago A => B
  • cats.Resource - Lortu eta kaleratu daitekeen baliabidea
  • Timer - tenporizadorea (pixka bat lo egiteko eta denbora tarteak neurtzeko aukera ematen dizu)
  • ContextShift - analogikoa ExecutionContext
  • Applicative β€” efektu motako klase bat, efektu indibidualak konbinatzeko aukera ematen duena (ia monada bat). Aplikazio konplexuagoetan erabiltzea hobe dela dirudi Monad/ConcurrentEffect.

Funtzio sinadura hau erabiliz hainbat zerbitzu 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](()))
  }

(Cm. iturria, eta bertan beste zerbitzu batzuk ezartzen dira - oihartzun zerbitzua, echo bezeroa
ΠΈ bizitza osorako kontrolagailuak.)

Nodo bat hainbat zerbitzu abiarazi ditzakeen objektu bat da (baliabide-kate baten abiarazte Cake Pattern-ek bermatzen du):

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

Kontuan izan nodo honetarako behar den konfigurazio mota zehatza zehazten ari garela. Zerbitzu jakin batek eskatzen duen konfigurazio motaren bat zehaztea ahazten badugu, konpilazio-errore bat egongo da. Era berean, ezingo dugu nodo bat abiarazi behar diren datu guztiak dituen mota egokiko objekturen bat ematen ez badugu.

Ostalariaren izenaren ebazpena

Urruneko ostalari batera konektatzeko, benetako IP helbide bat behar dugu. Baliteke helbidea gainerako konfigurazioa baino geroago ezagutzea. Beraz, nodoaren IDa helbide batera mapatzen duen funtzio bat behar dugu:

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

Funtzio hau ezartzeko hainbat modu daude:

  1. Helbideak zabaldu aurretik ezagutzen badizkigute, orduan Scala kodea sor dezakegu
    helbideak eta gero exekutatu eraikitzea. Honek probak konpilatu eta exekutatu egingo ditu.
    Kasu honetan, funtzioa estatikoki ezagutuko da eta kodean mapping gisa irudika daiteke Map[NodeId, NodeAddress].
  2. Zenbait kasutan, benetako helbidea nodoa hasi ondoren bakarrik ezagutzen da.
    Kasu honetan, beste nodoen aurretik exekutatzen den "aurkikuntza-zerbitzua" ezar dezakegu eta nodo guztiak zerbitzu honetan erregistratuko dira eta beste nodoen helbideak eskatuko dituzte.
  3. Aldatu ahal badugu /etc/hosts, orduan aurrez zehaztutako ostalari-izenak erabil ditzakezu (adibidez my-project-main-node ΠΈ echo-backend) eta lotu besterik gabe izen hauek
    hedapenean IP helbideekin.

Post honetan ez ditugu kasu hauek zehatzago aztertuko. Guretzat
jostailu adibide batean, nodo guztiek IP helbide bera izango dute - 127.0.0.1.

Ondoren, sistema banatu baterako bi aukera kontuan hartuko ditugu:

  1. Zerbitzu guztiak nodo batean jartzea.
  2. Eta oihartzun zerbitzua eta oihartzun bezeroa nodo desberdinetan ostatzea.

Konfigurazioa nodo bat:

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

Objektuak bezeroaren eta zerbitzariaren konfigurazioa ezartzen du. Denbora-bizitzeko konfigurazioa ere erabiltzen da, tartearen ondoren lifetime programa amaitu. (Ktrl-C ere funtzionatzen du eta baliabide guztiak behar bezala askatzen ditu.)

Konfigurazio- eta ezarpen-ezaugarrien multzo bera erabil daiteke osatutako sistema bat sortzeko bi nodo bereizi:

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

Garrantzitsua! Kontuan izan zerbitzuak nola lotzen diren. Nodo batek inplementatutako zerbitzu bat beste nodo baten menpekotasun metodoaren inplementazio gisa zehazten dugu. Mendekotasun mota konpilatzaileak egiaztatzen du, izan ere protokolo mota dauka. Exekutatzen denean, menpekotasunak helburu-nodoaren ID zuzena izango du. Eskema honi esker, ataka-zenbakia zehatz-mehatz behin zehazten dugu eta beti bermatzen dugu ataka zuzenari erreferentzia egitea.

Bi sistema-nodoren ezarpena

Konfigurazio honetarako, zerbitzuen inplementazio berdinak erabiltzen ditugu aldaketarik gabe. Desberdintasun bakarra zera da: zerbitzu multzo desberdinak ezartzen dituzten bi objektu ditugula:

  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
  }

Lehenengo nodoak zerbitzaria inplementatzen du eta zerbitzariaren konfigurazioa besterik ez du behar. Bigarren nodoak bezeroa inplementatzen du eta konfigurazioaren beste zati bat erabiltzen du. Gainera, bi nodoek bizitza osorako kudeaketa behar dute. Zerbitzari-nodoa mugagabean exekutatzen da gelditu arte SIGTERM'om, eta bezero-nodoa amaitzen da denbora pixka bat igaro ondoren. cm. abiarazle aplikazioa.

Garapen prozesu orokorra

Ikus dezagun konfigurazio-ikuspegi honek garapen-prozesu orokorrari nola eragiten dion.

Konfigurazioa gainerako kodearekin batera konpilatuko da eta artefaktu bat (.jar) sortuko da. Badirudi zentzuzkoa dela konfigurazioa aparteko artefaktu batean jartzea. Hau da, kode berean oinarritutako hainbat konfigurazio izan ditzakegulako. Berriz ere, konfigurazio-adar ezberdinei dagozkien artefaktuak sortzea posible da. Liburutegien bertsio zehatzen menpekotasunak konfigurazioarekin batera gordetzen dira, eta bertsio horiek betiko gordetzen dira konfigurazioaren bertsio hori zabaltzea erabakitzen dugunean.

Edozein konfigurazio-aldaketa kode aldaketa bihurtzen da. Eta horregatik, bakoitza
aldaketa kalitatea bermatzeko prozesu arruntak estaliko du:

Txartela akatsen jarraitzailean -> PR -> berrikuspena -> batu adar garrantzitsuekin ->
integrazioa -> hedapena

Konfigurazio konpilatua ezartzearen ondorio nagusiak hauek dira:

  1. Konfigurazioa koherentea izango da sistema banatuko nodo guztietan. Nodo guztiek iturri bakar batetik konfigurazio bera jasotzen dutelako.

  2. Arazoa da konfigurazioa nodo bakarrean aldatzea. Beraz, "konfigurazio-noraeza" nekez da.

  3. Zailagoa da konfigurazioan aldaketa txikiak egitea.

  4. Konfigurazio-aldaketa gehienak garapen prozesu orokorrean gertatuko dira eta berrikuspenaren gai izango dira.

Produkzioaren konfigurazioa gordetzeko beste biltegi bat behar al dut? Konfigurazio honek pasahitzak eta sarbidea mugatu nahi genukeen beste informazio sentikorra izan dezake. Horretan oinarrituta, badirudi zentzuzkoa dela azken konfigurazioa aparteko biltegi batean gordetzea. Konfigurazioa bi zatitan zati dezakezu: publikoki eskuragarri dauden konfigurazio-ezarpenak dituena eta ezarpen mugatuak dituena. Horri esker, garatzaile gehienek ezarpen arruntetarako sarbidea izango dute. Bereizketa hori erraz lortzen da balio lehenetsiak dituzten bitarteko ezaugarriak erabiliz.

Aldaera posibleak

Saia gaitezen konpilatutako konfigurazioa alternatiba arrunt batzuekin konparatzen:

  1. Testu-fitxategia xede-makinan.
  2. Gako-balioen biltegi zentralizatua (etcd/zookeeper).
  3. Prozesua berrabiarazi gabe birkonfiguratu/berrabia daitezkeen osagaiak.
  4. Konfigurazioa artefaktu eta bertsio-kontroletik kanpo biltegiratzea.

Testu-fitxategiek malgutasun handia eskaintzen dute aldaketa txikiei dagokienez. Sistema-administratzaileak urruneko nodoan saioa egin dezake, dagokion fitxategietan aldaketak egin eta zerbitzua berrabiarazi. Sistema handietarako, ordea, baliteke malgutasun hori desiragarria ez izatea. Egindako aldaketek ez dute arrastorik uzten beste sistemetan. Inork ez ditu aldaketak berrikusten. Zaila da zehaztea nork egin dituen aldaketak eta zer arrazoirengatik. Aldaketak ez dira probatzen. Sistema banatzen bada, administratzaileak beste nodo batzuetan dagokion aldaketa egitea ahaztu dezake.

(Kontuan izan behar da, halaber, konpilatutako konfigurazioa erabiltzeak ez duela etorkizunean testu fitxategiak erabiltzeko aukera ixten. Nahikoa izango da irteeraren mota bera sortzen duen analizatzaile eta baliozkotzaile bat gehitzea. Config, eta testu fitxategiak erabil ditzakezu. Berehala ondorioztatzen da konfigurazio konpilatua duen sistema baten konplexutasuna testu fitxategiak erabiltzen dituen sistema baten konplexutasuna baino zertxobait txikiagoa dela, zeren eta testu-fitxategiek kode gehigarria behar dute.)

Gako-balioen biltegi zentralizatua mekanismo ona da banatutako aplikazio baten meta-parametroak banatzeko. Erabaki behar dugu zer diren konfigurazio-parametroak eta zer diren datuak besterik. Egin dezagun funtzio bat C => A => B, eta parametroak C gutxitan aldaketak, eta datuak A - maiz. Kasu honetan hori esan dezakegu C - konfigurazio-parametroak, eta A - datuak. Badirudi konfigurazio-parametroak datuetatik desberdinak direla, oro har, datuak baino maizago aldatzen direlako. Gainera, datuak iturri batetik datoz normalean (erabiltzailearengandik), eta konfigurazio-parametroak beste batetik (sistemaren administratzailetik).

Gutxitan aldatzen diren parametroak programa berrabiarazi gabe eguneratu behar badira, horrek askotan programaren konplikazioa eragin dezake, nolabait parametroak entregatu, gorde, analizatu eta egiaztatu eta balio okerrak prozesatu beharko ditugulako. Hori dela eta, programaren konplexutasuna murrizteko ikuspegitik, zentzuzkoa da programaren funtzionamenduan alda daitezkeen parametroen kopurua murriztea (edo halako parametroak batere onartzen ez izatea).

Post honen helburuetarako, parametro estatikoak eta dinamikoak bereiziko ditugu. Zerbitzuaren logikak programaren funtzionamenduan zehar parametroak aldatzea eskatzen badu, honelako parametroak dinamiko deituko ditugu. Bestela, aukerak estatikoak dira eta konfigurazio konpilatuaren bidez konfigura daitezke. Birkonfigurazio dinamikorako, programaren zatiak parametro berriekin berrabiarazteko mekanismo bat behar dugu, sistema eragilearen prozesuak berrabiarazten diren antzera. (Gure ustez, denbora errealeko birkonfigurazioa saihestea komeni da, horrek sistemaren konplexutasuna areagotzen baitu. Ahal izanez gero, hobe da prozesuak berrabiarazteko sistema eragilearen gaitasun estandarrak erabiltzea.)

Jendeak birkonfigurazio dinamikoa kontuan hartzen duen konfigurazio estatikoa erabiltzearen alderdi garrantzitsu bat konfigurazio eguneratze baten ondoren sistemak berrabiarazteko behar duen denbora da (gelditasuna). Izan ere, konfigurazio estatikoan aldaketak egin behar baditugu, sistema berrabiarazi beharko dugu balio berriak indarrean izan daitezen. Geldialdien arazoa larritasunaz aldatzen da sistema desberdinetarako. Zenbait kasutan, berrabiaraztea programatu dezakezu karga minimoa denean. Etengabeko zerbitzua eman behar baduzu, inplementatu dezakezu AWS ELB konexioa drainatzea. Aldi berean, sistema berrabiarazi behar dugunean, sistema honen instantzia paralelo bat abiarazten dugu, orekatzailea bertara aldatzen dugu eta konexio zaharrak amaitu arte itxarongo dugu. Konexio zahar guztiak amaitu ondoren, sistemaren instantzia zaharra itzali dugu.

Ikus dezagun orain konfigurazioa artefaktuaren barruan edo kanpoan gordetzearen arazoa. Konfigurazioa artefaktu baten barruan gordetzen badugu, gutxienez konfigurazioaren zuzentasuna egiaztatzeko aukera izan dugu artefaktuaren muntaketan zehar. Konfigurazioa kontrolatutako artefaktutik kanpo badago, zaila da fitxategi honetan aldaketak nork egin dituen eta zergatik jarraitzea. Zenbateko garrantzia du? Gure ustez, ekoizpen-sistema askorentzat garrantzitsua da konfigurazio egonkorra eta kalitate handikoa izatea.

Artefaktu baten bertsioak noiz sortu den, zer balio dituen, zein funtzio gaitu/desgaitu eta konfigurazioaren edozein aldaketaren arduraduna den zehazteko aukera ematen du. Jakina, konfigurazioa artefaktu baten barruan gordetzeak ahalegin bat eskatzen du, beraz, erabaki informatua hartu behar duzu.

Pros eta Cons

Proposatutako teknologiaren alde onak eta txarrak aztertu nahi nituzke.

Abantailak

Jarraian, konpilatutako sistema banatuaren konfigurazio baten ezaugarri nagusien zerrenda dago:

  1. Konfigurazio estatikoa egiaztatzea. Hori ziur egoteko aukera ematen du
    konfigurazioa zuzena da.
  2. Konfigurazio hizkuntza aberatsa. Normalean, beste konfigurazio-metodo batzuk kate-aldagaien ordezkapenera mugatzen dira gehienez. Scala erabiltzean, hizkuntza-eginbide ugari daude erabilgarri zure konfigurazioa hobetzeko. Adibidez, erabil dezakegu
    balio lehenetsietarako ezaugarriak, parametroak taldekatzeko objektuak erabiliz, behin bakarrik deklaratutako vals-ak (DRY) aipa ditzakegu barne-esparruan. Edozein klase instantzia ditzakezu zuzenean konfigurazio barruan (Seq, Map, klase pertsonalizatuak).
  3. DSL. Scalak hizkuntza-funtzio ugari ditu, DSL bat sortzea errazten dutenak. Ezaugarri hauek aprobetxatu eta xede-taldearentzat erosoagoa den konfigurazio-lengoaia ezartzea posible da, konfigurazioa gutxienez domeinuko adituek irakur dezaten. Espezialistek, adibidez, konfigurazioa berrikusteko prozesuan parte hartu dezakete.
  4. Nodoen arteko osotasuna eta sinkronia. Banatutako sistema osoaren konfigurazioa puntu bakarrean gordeta edukitzearen abantailetako bat balio guztiak zehatz-mehatz behin deklaratzen direla da, eta gero berrerabiltzen direla behar diren lekuan. Portuak deklaratzeko fantasma motak erabiltzeak ziurtatzen du nodoek protokolo bateragarriak erabiltzen ari direla sistemaren konfigurazio zuzen guztietan. Nodoen artean derrigorrezko menpekotasun esplizituak izateak zerbitzu guztiak konektatuta daudela ziurtatzen du.
  5. Kalitate handiko aldaketak. Garapen prozesu komun bat erabiliz konfigurazioan aldaketak egiteak aukera ematen du konfiguraziorako ere kalitate estandar altuak lortzea.
  6. Aldibereko konfigurazioa eguneratzea. Konfigurazio-aldaketen ondoren sistema automatikoki inplementatzea ziurtatzen da nodo guztiak eguneratuta daudela.
  7. Aplikazioa sinplifikatuz. Aplikazioak ez du analisia, konfigurazioa egiaztatzea edo balio okerrak maneiatzea behar. Horrek aplikazioaren konplexutasuna murrizten du. (Gure adibidean ikusitako konfigurazio konplexutasun batzuk ez dira konfigurazio konpilatuaren atributu bat, motako segurtasun handiagoa eskaintzeko nahiak bultzatutako erabaki kontziente bat baizik.) Nahiko erraza da ohiko konfiguraziora itzultzea - ​​falta dena inplementatu besterik ez dago. zatiak. Hori dela eta, adibidez, konfigurazio konpilatu batekin has zaitezke, beharrezkoak ez diren zatien ezarpena benetan beharrezkoa den unera arte atzeratuz.
  8. Konfigurazio egiaztatua. Konfigurazio-aldaketek beste edozein aldaketen ohiko patua jarraitzen dutenez, lortzen dugun irteera bertsio bakarra duen artefaktua da. Horri esker, adibidez, beharrezkoa bada konfigurazioaren aurreko bertsio batera itzul gaitezke. Duela urtebeteko konfigurazioa ere erabil dezakegu eta sistemak berdin funtzionatuko du. Konfigurazio egonkorrak sistema banatu baten aurreikusgarritasuna eta fidagarritasuna hobetzen ditu. Konfigurazioa konpilazio fasean konpontzen denez, nahiko zaila da ekoizpenean faltsutzea.
  9. Modulartasuna. Proposatutako esparrua modularra da eta moduluak modu ezberdinetan konbina daitezke sistema desberdinak sortzeko. Bereziki, sistema konfigura dezakezu nodo bakarrean exekutatzeko gauzatze batean, eta nodo anitzetan beste batean. Sistemaren ekoizpen instantzietarako hainbat konfigurazio sor ditzakezu.
  10. Probak. Banakako zerbitzuak objektu simulatuekin ordezkatuz, probatzeko erosoak diren sistemaren hainbat bertsio lor ditzakezu.
  11. Integrazio probak. Banatutako sistema osorako konfigurazio bakarra izateak osagai guztiak ingurune kontrolatu batean exekutatzeko aukera ematen du integrazio-proben zati gisa. Erraza da emulatzea, adibidez, nodo batzuk eskuragarri bihurtzen diren egoera.

Desabantailak eta mugak

Konpilatutako konfigurazioa beste konfigurazio ikuspegietatik desberdina da eta baliteke aplikazio batzuetarako egokia ez izatea. Jarraian, desabantaila batzuk daude:

  1. Konfigurazio estatikoa. Batzuetan, konfigurazioa azkar zuzendu behar duzu ekoizpenean, babes-mekanismo guztiak saihestuz. Planteamendu honekin zailagoa izan daiteke. Gutxienez, konpilazioa eta hedapen automatikoa beharrezkoak izango dira oraindik. Hau planteamenduaren ezaugarri erabilgarria eta desabantaila da kasu batzuetan.
  2. Konfigurazioa sortzea. Konfigurazio-fitxategia tresna automatiko batek sortzen badu, ahalegin gehigarriak beharko dira eraikitze-scripta integratzeko.
  3. Tresnak. Gaur egun, konfigurazioarekin lan egiteko diseinatutako utilitateak eta teknikak testu fitxategietan oinarritzen dira. Utilitate/teknika horiek guztiak ez dira erabilgarri egongo konfigurazio konpilatu batean.
  4. Jarrera aldaketa behar da. Garatzaileak eta DevOps testu-fitxategietara ohituta daude. Konfigurazio bat osatzeko ideia bera ustekabekoa eta ezohikoa izan daiteke eta baztertzea eragin dezake.
  5. Kalitate handiko garapen-prozesua behar da. Konpilatutako konfigurazioa eroso erabiltzeko, beharrezkoa da aplikazioa (CI/CD) eraikitzeko eta hedatzeko prozesuaren automatizazio osoa. Bestela nahiko deserosoa izango da.

Aztertu gaitezen kontuan hartutako adibidearen zenbait mugatan, konfigurazio konpilatuaren ideiarekin lotuta ez daudenak:

  1. Nodoak erabiltzen ez duen beharrezkoa ez den konfigurazio-informazioa ematen badugu, konpilatzaileak ez digu lagunduko falta den inplementazioa detektatzen. Arazo hau pastelaren eredua alde batera utzi eta mota zurrunagoak erabiliz konpon daiteke, adibidez, HList edo datu mota aljebraikoak (kasu-klaseak) konfigurazioa irudikatzeko.
  2. Konfigurazio-fitxategian konfigurazio-fitxategiarekin erlazionatuta ez dauden lerroak daude: (package, import,objektuen deklarazioak; override defbalio lehenetsiak dituzten parametroetarako). Hau partzialki saihestu daiteke zure DSL propioa ezartzen baduzu. Horrez gain, beste konfigurazio mota batzuek (adibidez, XML-ak) muga batzuk ere ezartzen dizkiote fitxategi-egiturari.
  3. Argitalpen honen helburuetarako, ez dugu antzeko nodoen multzo baten birkonfigurazio dinamikoa kontuan hartzen ari.

Ondorioa

Post honetan, iturburu-kodean konfigurazioa irudikatzeko ideia aztertu dugu Scala motako sistemaren gaitasun aurreratuak erabiliz. Ikuspegi hau hainbat aplikaziotan erabil daiteke xml edo testu fitxategietan oinarritutako konfigurazio metodo tradizionalen ordez. Gure adibidea Scala-n inplementatuta dagoen arren, ideia berdinak beste lengoaia konpilatu batzuetara transferi daitezke (esaterako, Kotlin, C#, Swift, ...). Ikuspegi hau hurrengo proiektuetako batean probatu dezakezu eta, funtzionatzen ez badu, joan testu fitxategira, falta diren zatiak gehituz.

Jakina, konpilatutako konfigurazio batek kalitate handiko garapen-prozesua behar du. Horren truke, konfigurazioen kalitate handia eta fidagarritasuna bermatzen da.

Kontuan hartutako ikuspegia hedatu daiteke:

  1. Makroak erabil ditzakezu konpilazio garaiko egiaztapenak egiteko.
  2. DSL bat inplementatu dezakezu konfigurazioa azken erabiltzaileentzat eskuragarri dagoen moduan aurkezteko.
  3. Baliabideen kudeaketa dinamikoa ezar dezakezu konfigurazio doikuntza automatikoarekin. Adibidez, kluster bateko nodo kopurua aldatzeak (1) nodo bakoitzak konfigurazio apur bat desberdina jasotzea eskatzen du; (2) kluster-kudeatzaileak nodo berriei buruzko informazioa jaso zuen.

Eskerrak

Eskerrak eman nahi dizkiet Andrei Saksonov, Pavel Popov eta Anton Nekhaev-i artikulu zirriborroari egindako kritika eraikitzaileagatik.

Iturria: www.habr.com

Gehitu iruzkin berria