Apkopota izplatītās sistēmas konfigurācija

Es gribētu jums pastāstÄ«t vienu interesantu mehānismu darbam ar sadalÄ«tas sistēmas konfigurāciju. Konfigurācija tiek attēlota tieÅ”i kompilētā valodā (Scala), izmantojot droÅ”us veidus. Å ajā rakstā ir sniegts Ŕādas konfigurācijas piemērs un apskatÄ«ti dažādi apkopotās konfigurācijas ievieÅ”anas aspekti kopējā izstrādes procesā.

Apkopota izplatītās sistēmas konfigurācija

(Angļu)

Ievads

Uzticamas sadalÄ«tas sistēmas izveide nozÄ«mē, ka visi mezgli izmanto pareizo konfigurāciju, kas ir sinhronizēta ar citiem mezgliem. DevOps tehnoloÄ£ijas (terraform, ansible vai kaut kas lÄ«dzÄ«gs) parasti tiek izmantotas, lai automātiski Ä£enerētu konfigurācijas failus (bieži raksturÄ«gi katram mezglam). Mēs arÄ« vēlamies bÅ«t pārliecināti, ka visi saziņas mezgli izmanto identiskus protokolus (ieskaitot to paÅ”u versiju). Pretējā gadÄ«jumā mÅ«su izplatÄ«tajā sistēmā tiks iebÅ«vēta nesaderÄ«ba. JVM pasaulē Ŕīs prasÄ«bas sekas ir tādas, ka visur ir jāizmanto viena un tā pati bibliotēkas versija, kurā ir protokola ziņojumi.

Kā ar izplatÄ«tās sistēmas testÄ“Å”anu? Protams, mēs pieņemam, ka visiem komponentiem ir vienÄ«bu testi, pirms mēs pārietam uz integrācijas testÄ“Å”anu. (Lai mēs varētu ekstrapolēt testa rezultātus izpildlaikā, mums ir arÄ« jānodroÅ”ina identisks bibliotēku kopums testÄ“Å”anas stadijā un izpildlaikā.)

Strādājot ar integrācijas testiem, bieži vien ir vieglāk izmantot vienu un to paÅ”u klases ceļu visos mezglos. Viss, kas mums jādara, ir nodroÅ”ināt, ka izpildlaikā tiek izmantots tas pats klases ceļŔ. (Lai gan ir pilnÄ«gi iespējams palaist dažādus mezglus ar dažādiem klases ceļiem, tas sarežģī vispārējo konfigurāciju un sarežģī izvietoÅ”anas un integrācijas testus.) Å Ä«s ziņas nolÅ«kos mēs pieņemam, ka visi mezgli izmantos vienu un to paÅ”u klases ceļu.

Konfigurācija attÄ«stās lÄ«dz ar lietojumprogrammu. Mēs izmantojam versijas, lai identificētu dažādus programmas evolÅ«cijas posmus. Å Ä·iet loÄ£iski identificēt arÄ« dažādas konfigurāciju versijas. Un ievietojiet versiju kontroles sistēmā paÅ”u konfigurāciju. Ja ražoÅ”anā ir tikai viena konfigurācija, mēs varam vienkārÅ”i izmantot versijas numuru. Ja mēs izmantojam daudzus ražoÅ”anas gadÄ«jumus, mums bÅ«s nepiecieÅ”ami vairāki
konfigurācijas filiāles un papildu etiķete papildus versijai (piemēram, filiāles nosaukums). Tādā veidā mēs varam skaidri noteikt precīzu konfigurāciju. Katrs konfigurācijas identifikators unikāli atbilst noteiktai sadalīto mezglu, portu, ārējo resursu un bibliotēkas versiju kombinācijai. Šajā ziņojumā mēs pieņemsim, ka ir tikai viens atzars, un mēs varam noteikt konfigurāciju parastajā veidā, izmantojot trīs skaitļus, kas atdalīti ar punktu (1.2.3).

MÅ«sdienu vidē konfigurācijas faili reti tiek izveidoti manuāli. Biežāk tie tiek Ä£enerēti izvietoÅ”anas laikā un vairs netiek aiztikti (tā nelauz neko). Rodas dabisks jautājums: kāpēc mēs joprojām izmantojam teksta formātu, lai saglabātu konfigurāciju? Å Ä·iet, ka dzÄ«votspējÄ«ga alternatÄ«va ir iespēja izmantot parasto kodu konfigurācijai un gÅ«t labumu no kompilÄ“Å”anas laika pārbaudēm.

Å ajā ierakstā mēs izpētÄ«sim ideju par konfigurācijas attēloÅ”anu apkopotā artefaktā.

Sastādīta konfigurācija

Å ajā sadaļā ir sniegts statiskas kompilētas konfigurācijas piemērs. Tiek ieviesti divi vienkārÅ”i pakalpojumi - atbalss pakalpojums un atbalss pakalpojuma klients. Pamatojoties uz Å”iem diviem pakalpojumiem, tiek montētas divas sistēmas opcijas. Vienā variantā abi pakalpojumi atrodas vienā mezglā, citā variantā - dažādos mezglos.

Parasti izplatītā sistēma satur vairākus mezglus. Jūs varat identificēt mezglus, izmantojot noteikta veida vērtības NodeId:

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

vai

case class NodeId(hostName: String)

vai pat

object Singleton
type NodeId = Singleton.type

Mezgli veic dažādas lomas, tie vada pakalpojumus un starp tiem var izveidot TCP/HTTP savienojumus.

Lai aprakstÄ«tu TCP savienojumu, mums ir nepiecieÅ”ams vismaz porta numurs. Mēs arÄ« vēlamies atspoguļot protokolu, kas tiek atbalstÄ«ts Å”ajā portā, lai nodroÅ”inātu, ka gan klients, gan serveris izmanto vienu un to paÅ”u protokolu. Mēs aprakstÄ«sim savienojumu, izmantojot Ŕādu klasi:

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

kur Port - tikai vesels skaitlis Int norādot pieņemamo vērtību diapazonu:

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

Rafinēti veidi

SkatÄ«t bibliotēku rafinēts Šø mans Ziņot. ÄŖsāk sakot, bibliotēka ļauj pievienot ierobežojumus tipiem, kas tiek pārbaudÄ«ti kompilÄ“Å”anas laikā. Å ajā gadÄ«jumā derÄ«gās porta numuru vērtÄ«bas ir 16 bitu veseli skaitļi. Kompilētai konfigurācijai pilnveidotās bibliotēkas izmantoÅ”ana nav obligāta, taču tā uzlabo kompilatora spēju pārbaudÄ«t konfigurāciju.

HTTP (REST) protokoliem papildus porta numuram var bÅ«t nepiecieÅ”ams arÄ« ceļŔ uz pakalpojumu:

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

Fantoma veidi

Lai identificētu protokolu kompilÄ“Å”anas laikā, mēs izmantojam tipa parametru, kas klasē netiek izmantots. Å is lēmums ir saistÄ«ts ar faktu, ka mēs neizmantojam protokola gadÄ«jumu izpildes laikā, bet mēs vēlamies, lai kompilators pārbaudÄ«tu protokola saderÄ«bu. Norādot protokolu, mēs nevarēsim nodot neatbilstoÅ”u pakalpojumu kā atkarÄ«bu.

Viens no izplatītākajiem protokoliem ir REST API ar Json serializāciju:

sealed trait JsonHttpRestProtocol[RequestMessage, ResponseMessage]

kur RequestMessage - pieprasījuma veids, ResponseMessage - atbildes veids.
Protams, mēs varam izmantot citus protokolu aprakstus, kas nodroÅ”ina mums nepiecieÅ”amo apraksta precizitāti.

Å Ä«s ziņas vajadzÄ«bām mēs izmantosim vienkārÅ”otu protokola versiju:

sealed trait SimpleHttpGetRest[RequestMessage, ResponseMessage]

Šeit pieprasījums ir virkne, kas pievienota URL, un atbilde ir atgrieztā virkne HTTP atbildes pamattekstā.

Pakalpojuma konfigurāciju apraksta pakalpojuma nosaukums, porti un atkarÄ«bas. Å os elementus programmā Scala var attēlot vairākos veidos (piemēram, HList-s, algebrisko datu tipi). Å Ä«s ziņas nolÅ«kos mēs izmantosim kÅ«kas modeli un attēlosim moduļus, izmantojot trait'ov. (KÅ«kas raksts nav obligāts Ŕīs pieejas elements. Tā ir tikai viena no iespējām.)

Atkarības starp pakalpojumiem var attēlot kā metodes, kas atgriež portus EndPointcitu mezglu:

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

Lai izveidotu atbalss pakalpojumu, jums ir nepiecieÅ”ams tikai porta numurs un norāde, ka ports atbalsta atbalss protokolu. Mēs varam nenorādÄ«t konkrētu portu, jo... iezÄ«mes ļauj deklarēt metodes bez ievieÅ”anas (abstraktās metodes). Å ajā gadÄ«jumā, veidojot konkrētu konfigurāciju, kompilators mums prasÄ«tu nodroÅ”ināt abstraktās metodes ievieÅ”anu un norādÄ«t porta numuru. Tā kā mēs esam ieviesuÅ”i metodi, veidojot konkrētu konfigurāciju, mēs varam nenorādÄ«t citu portu. Tiks izmantota noklusējuma vērtÄ«ba.

Klienta konfigurācijā mēs deklarējam atkarību no atbalss pakalpojuma:

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

AtkarÄ«ba ir tāda paÅ”a veida kā eksportētais pakalpojums echoService. Jo Ä«paÅ”i echo klientā mums ir nepiecieÅ”ams tas pats protokols. Tāpēc, savienojot divus pakalpojumus, varam bÅ«t pārliecināti, ka viss darbosies pareizi.

Pakalpojumu ievieŔana

Lai sāktu un apturētu pakalpojumu, ir nepiecieÅ”ama funkcija. (Spēja apturēt pakalpojumu ir ļoti svarÄ«ga testÄ“Å”anai.) Atkal ir vairākas iespējas Ŕādas funkcijas ievieÅ”anai (piemēram, mēs varētu izmantot tipu klases, pamatojoties uz konfigurācijas veidu). Å Ä«s ziņas nolÅ«kos mēs izmantosim kÅ«kas modeli. Mēs pārstāvēsim pakalpojumu, izmantojot klasi cats.Resource, jo Å Ä« klase jau nodroÅ”ina lÄ«dzekļus, lai droÅ”i garantētu resursu atbrÄ«voÅ”anu problēmu gadÄ«jumā. Lai iegÅ«tu resursu, mums ir jānodroÅ”ina konfigurācija un gatavs izpildlaika konteksts. Pakalpojuma palaiÅ”anas funkcija var izskatÄ«ties Ŕādi:

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

kur

  • Config ā€” Ŕī pakalpojuma konfigurācijas veids
  • AddressResolver ā€” izpildlaika objekts, kas ļauj uzzināt citu mezglu adreses (skatÄ«t zemāk)

un citi veidi no bibliotēkas cats:

  • F[_] ā€” efekta veids (vienkārŔākajā gadÄ«jumā F[A] varētu bÅ«t tikai funkcija () => A. Å ajā amatā mēs izmantosim cats.IO.)
  • Reader[A,B] - vairāk vai mazāk sinonÄ«ms funkcijai A => B
  • cats.Resource - resurss, ko var iegÅ«t un atbrÄ«vot
  • Timer ā€” taimeris (ļauj uz brÄ«di aizmigt un izmērÄ«t laika intervālus)
  • ContextShift - analogs ExecutionContext
  • Applicative ā€” efektu tipa klase, kas ļauj apvienot atseviŔķus efektus (gandrÄ«z monāde). Sarežģītākos lietojumos Ŕķiet labāk izmantot Monad/ConcurrentEffect.

Izmantojot Å”o funkcijas parakstu, mēs varam realizēt vairākus pakalpojumus. Piemēram, pakalpojums, kas neko nedara:

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

(cm. avots, kurā tiek īstenoti citi pakalpojumi - atbalss pakalpojums, atbalss klients
Šø mūža kontrolieri.)

Mezgls ir objekts, kas var palaist vairākus pakalpojumus (resursu ķēdes palaiÅ”anu nodroÅ”ina kÅ«ka modelis):

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

LÅ«dzu, ņemiet vērā, ka mēs norādām precÄ«zu Å”im mezglam nepiecieÅ”amo konfigurācijas veidu. Ja aizmirstam norādÄ«t kādu no konfigurācijas veidiem, kas nepiecieÅ”ami konkrētam pakalpojumam, radÄ«sies kompilācijas kļūda. Tāpat mēs nevarēsim palaist mezglu, ja vien mēs nenodroÅ”ināsim kādu atbilstoÅ”a tipa objektu ar visiem nepiecieÅ”amajiem datiem.

Resursdatora nosaukuma izŔķirtspēja

Lai izveidotu savienojumu ar attālo resursdatoru, mums ir nepiecieÅ”ama Ä«sta IP adrese. Iespējams, ka adrese kļūs zināma vēlāk nekā pārējā konfigurācija. Tātad mums ir nepiecieÅ”ama funkcija, kas savieno mezgla ID ar adresi:

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

Ir vairāki veidi, kā īstenot Ŕo funkciju:

  1. Ja adreses mums kļūst zināmas pirms izvietoÅ”anas, mēs varam Ä£enerēt Scala kodu ar
    adreses un pēc tam palaidiet būvniecību. Tas apkopos un izpildīs testus.
    Šajā gadījumā funkcija būs zināma statiski, un to var attēlot kodā kā kartējumu Map[NodeId, NodeAddress].
  2. Dažos gadÄ«jumos faktiskā adrese ir zināma tikai pēc mezgla palaiÅ”anas.
    Å ajā gadÄ«jumā mēs varam ieviest ā€œatklāŔanas pakalpojumuā€, kas darbojas pirms citiem mezgliem, un visi mezgli reÄ£istrēsies Å”ajā pakalpojumā un pieprasÄ«s citu mezglu adreses.
  3. Ja mēs varam mainÄ«t /etc/hosts, tad varat izmantot iepriekÅ” definētus resursdatora nosaukumus (piemēram, my-project-main-node Šø echo-backend) un vienkārÅ”i savienojiet Å”os nosaukumus
    ar IP adresēm izvietoÅ”anas laikā.

Å ajā rakstā mēs Å”os gadÄ«jumus neapskatÄ«sim sÄ«kāk. PriekÅ” mÅ«su
rotaļlietas piemērā visiem mezgliem būs viena un tā pati IP adrese - 127.0.0.1.

Tālāk mēs apsveram divas izplatītas sistēmas iespējas:

  1. Visu pakalpojumu ievietoŔana vienā mezglā.
  2. Un atbalss pakalpojuma un atbalss klienta mitināŔana dažādos mezglos.

Konfigurācija priekŔ viens mezgls:

Viena mezgla konfigurācija

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

Objekts realizē gan klienta, gan servera konfigurāciju. Tiek izmantota arī dzīves laika konfigurācija, lai pēc intervāla lifetime pārtraukt programmu. (Strādā arī Ctrl-C un pareizi atbrīvo visus resursus.)

To paÅ”u konfigurācijas un ievieÅ”anas pazÄ«mju kopu var izmantot, lai izveidotu sistēmu, kas sastāv no divi atseviŔķi mezgli:

Divu mezglu konfigurācija

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

SvarÄ«gs! Ievērojiet, kā pakalpojumi ir saistÄ«ti. Mēs norādām viena mezgla ieviestu pakalpojumu kā cita mezgla atkarÄ«bas metodes ievieÅ”anu. AtkarÄ«bas veidu pārbauda kompilators, jo satur protokola veidu. Palaižot, atkarÄ«ba saturēs pareizo mērÄ·a mezgla ID. Pateicoties Å”ai shēmai, mēs precÄ«zi vienreiz norādām porta numuru un vienmēr tiek garantēta atsauce uz pareizo portu.

Divu sistēmas mezglu ievieÅ”ana

Å ai konfigurācijai mēs izmantojam tās paÅ”as pakalpojuma ievieÅ”anas bez izmaiņām. VienÄ«gā atŔķirÄ«ba ir tā, ka mums tagad ir divi objekti, kas ievieÅ” dažādas pakalpojumu kopas:

  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
  }

Pirmais mezgls ievieÅ” serveri, un tam ir nepiecieÅ”ama tikai servera konfigurācija. Otrais mezgls ievieÅ” klientu un izmanto citu konfigurācijas daļu. ArÄ« abiem mezgliem ir nepiecieÅ”ama mūža pārvaldÄ«ba. Servera mezgls darbojas bezgalÄ«gi, lÄ«dz tas tiek apturēts SIGTERM'om, un klienta mezgls pēc kāda laika tiek pārtraukts. Cm. palaiÅ”anas programma.

Vispārējs attīstības process

ApskatÄ«sim, kā Ŕī konfigurācijas pieeja ietekmē kopējo izstrādes procesu.

Konfigurācija tiks apkopota kopā ar pārējo kodu un tiks Ä£enerēts artefakts (.jar). Å Ä·iet, ka ir jēga ievietot konfigurāciju atseviŔķā artefaktā. Tas ir tāpēc, ka mums var bÅ«t vairākas konfigurācijas, kuru pamatā ir viens un tas pats kods. Atkal ir iespējams Ä£enerēt artefaktus, kas atbilst dažādām konfigurācijas zarām. AtkarÄ«bas no noteiktām bibliotēku versijām tiek saglabātas kopā ar konfigurāciju, un Ŕīs versijas tiek saglabātas uz visiem laikiem, kad mēs nolemjam izvietot Å”o konfigurācijas versiju.

Jebkuras konfigurācijas izmaiņas pārvērÅ”as par koda maiņu. Un tāpēc katrs
uz izmaiņām attieksies parastais kvalitātes nodroÅ”ināŔanas process:

Biļete kļūdu izsekotājā -> PR -> apskats -> sapludināŔana ar attiecÄ«gajām filiālēm ->
integrācija -> izvietoŔana

Galvenās kompilētās konfigurācijas ievieÅ”anas sekas ir:

  1. Konfigurācija bÅ«s konsekventa visos sadalÄ«tās sistēmas mezglos. Sakarā ar to, ka visi mezgli saņem vienu un to paÅ”u konfigurāciju no viena avota.

  2. Ir problemātiski mainÄ«t konfigurāciju tikai vienā no mezgliem. Tāpēc ā€œkonfigurācijas novirzeā€ ir maz ticama.

  3. Kļūst grūtāk veikt nelielas izmaiņas konfigurācijā.

  4. Lielākā daļa konfigurācijas izmaiņu notiks vispārējā izstrādes procesa ietvaros un tiks pārskatītas.

Vai man ir nepiecieÅ”ams atseviŔķs repozitorijs, lai saglabātu ražoÅ”anas konfigurāciju? Å ajā konfigurācijā var bÅ«t paroles un cita sensitÄ«va informācija, kurai mēs vēlamies ierobežot piekļuvi. Pamatojoties uz to, Ŕķiet, ir jēga glabāt galÄ«go konfigurāciju atseviŔķā repozitorijā. Konfigurāciju var sadalÄ«t divās daļās ā€” vienā satur publiski pieejamus konfigurācijas iestatÄ«jumus un otru, kas satur ierobežotus iestatÄ«jumus. Tas ļaus lielākajai daļai izstrādātāju piekļūt parastajiem iestatÄ«jumiem. Å o atdalÄ«Å”anu ir viegli panākt, izmantojot starpposma pazÄ«mes, kas satur noklusējuma vērtÄ«bas.

Iespējamās variācijas

Mēģināsim salīdzināt apkopoto konfigurāciju ar dažām izplatītām alternatīvām:

  1. Teksta fails mērÄ·a maŔīnā.
  2. Centralizēta atslēgu vērtību krātuve (etcd/zookeeper).
  3. Procesa komponenti, kurus var pārkonfigurēt/restartēt bez procesa restartÄ“Å”anas.
  4. Konfigurācijas glabāŔana ārpus artefaktu un versiju kontroles.

Teksta faili nodroÅ”ina ievērojamu elastÄ«bu nelielu izmaiņu ziņā. Sistēmas administrators var pieteikties attālajā mezglā, veikt izmaiņas attiecÄ«gajos failos un restartēt pakalpojumu. Tomēr lielām sistēmām Ŕāda elastÄ«ba var nebÅ«t vēlama. Veiktās izmaiņas neatstāj pēdas citās sistēmās. Izmaiņas neviens nepārskata. Ir grÅ«ti noteikt, kurÅ” tieÅ”i veica izmaiņas un kāda iemesla dēļ. Izmaiņas netiek pārbaudÄ«tas. Ja sistēma ir izplatÄ«ta, administrators var aizmirst veikt attiecÄ«gās izmaiņas citos mezglos.

(JāatzÄ«mē arÄ«, ka kompilētas konfigurācijas izmantoÅ”ana neizslēdz iespēju turpmāk izmantot teksta failus. Pietiks ar parsētāja un validatora pievienoÅ”anu, kas ražo tādu paÅ”u tipu kā izvadi Config, un varat izmantot teksta failus. No tā uzreiz izriet, ka sistēmas sarežģītÄ«ba ar kompilētu konfigurāciju ir nedaudz mazāka nekā tādas sistēmas sarežģītÄ«ba, kurā tiek izmantoti teksta faili, jo teksta failiem ir nepiecieÅ”ams papildu kods.)

Centralizēta atslēgu vērtÄ«bu krātuve ir labs mehānisms izplatÄ«tas lietojumprogrammas meta parametru izplatÄ«Å”anai. Mums ir jāizlemj, kas ir konfigurācijas parametri un kas ir tikai dati. Ä»aujiet mums veikt funkciju C => A => B, un parametri C reti mainās, un dati A - bieži. Å ajā gadÄ«jumā mēs to varam teikt C - konfigurācijas parametri un A - dati. Å Ä·iet, ka konfigurācijas parametri atŔķiras no datiem, jo ā€‹ā€‹tie parasti mainās retāk nekā dati. ArÄ« dati parasti nāk no viena avota (no lietotāja), bet konfigurācijas parametri no cita (no sistēmas administratora).

Ja reti mainÄ«gie parametri ir jāatjaunina, nerestartējot programmu, tas bieži vien var izraisÄ«t programmas sarežģījumus, jo mums bÅ«s kaut kādā veidā jāpiegādā parametri, jāsaglabā, parsē un jāpārbauda un jāapstrādā nepareizas vērtÄ«bas. Tāpēc no programmas sarežģītÄ«bas samazināŔanas viedokļa ir jēga samazināt to parametru skaitu, kas var mainÄ«ties programmas darbÄ«bas laikā (vai neatbalstÄ«t Ŕādus parametrus vispār).

Å Ä«s ziņas vajadzÄ«bām mēs noŔķirsim statiskos un dinamiskos parametrus. Ja servisa loÄ£ika prasa mainÄ«t parametrus programmas darbÄ«bas laikā, tad Ŕādus parametrus sauksim par dinamiskiem. Pretējā gadÄ«jumā opcijas ir statiskas, un tās var konfigurēt, izmantojot apkopoto konfigurāciju. Lai veiktu dinamisku pārkonfigurāciju, mums var bÅ«t nepiecieÅ”ams mehānisms, lai restartētu programmas daļas ar jauniem parametriem, lÄ«dzÄ«gi kā tiek restartēti operētājsistēmas procesi. (MÅ«suprāt, ir ieteicams izvairÄ«ties no reāllaika pārkonfigurācijas, jo tas palielina sistēmas sarežģītÄ«bu. Ja iespējams, procesu restartÄ“Å”anai labāk izmantot standarta OS iespējas.)

Viens svarÄ«gs statiskās konfigurācijas izmantoÅ”anas aspekts, kas liek cilvēkiem apsvērt dinamisku pārkonfigurāciju, ir laiks, kas nepiecieÅ”ams sistēmas atsāknÄ“Å”anai pēc konfigurācijas atjaunināŔanas (dÄ«kstāves). Faktiski, ja mums ir jāveic izmaiņas statiskajā konfigurācijā, mums bÅ«s jārestartē sistēma, lai jaunās vērtÄ«bas stātos spēkā. DÄ«kstāves problēma dažādās sistēmās atŔķiras pēc smaguma pakāpes. Dažos gadÄ«jumos varat ieplānot atsāknÄ“Å”anu laikā, kad slodze ir minimāla. Ja jums ir nepiecieÅ”ams nodroÅ”ināt nepārtrauktu pakalpojumu, varat to ieviest AWS ELB savienojuma iztukÅ”oÅ”ana. Tajā paŔā laikā, kad mums ir nepiecieÅ”ams restartēt sistēmu, mēs palaižam paralēlu Ŕīs sistēmas instanci, pārslēdzam uz to balansētāju un gaidām, kamēr vecie savienojumi tiks pabeigti. Kad visi vecie savienojumi ir pārtraukti, mēs izslēdzam veco sistēmas gadÄ«jumu.

Tagad apskatÄ«sim jautājumu par konfigurācijas saglabāŔanu artefaktā vai ārpus tā. Ja mēs glabājam konfigurāciju artefaktā, tad vismaz mums bija iespēja pārbaudÄ«t konfigurācijas pareizÄ«bu artefakta montāžas laikā. Ja konfigurācija ir ārpus kontrolētā artefakta, ir grÅ«ti izsekot, kurÅ” un kāpēc veica izmaiņas Å”ajā failā. Cik tas ir svarÄ«gi? MÅ«suprāt, daudzām ražoÅ”anas sistēmām ir svarÄ«ga stabila un kvalitatÄ«va konfigurācija.

Artefakta versija ļauj noteikt, kad tas tika izveidots, kādas vērtÄ«bas tas satur, kādas funkcijas ir iespējotas/atspējotas un kurÅ” ir atbildÄ«gs par jebkādām izmaiņām konfigurācijā. Protams, konfigurācijas saglabāŔana artefaktā prasa zināmas pÅ«les, tāpēc jums ir jāpieņem apzināts lēmums.

Plusi un mīnusi

Es vēlētos pakavēties pie piedāvātās tehnoloģijas plusiem un mīnusiem.

PriekŔrocības

Tālāk ir sniegts apkopotās izplatītās sistēmas konfigurācijas galveno funkciju saraksts:

  1. Statiskā konfigurācijas pārbaude. Ļauj jums par to pārliecināties
    konfigurācija ir pareiza.
  2. BagātÄ«ga konfigurācijas valoda. Parasti citas konfigurācijas metodes aprobežojas ar virknes mainÄ«go aizstāŔanu. Lietojot Scala, ir pieejams plaÅ”s valodu funkciju klāsts, lai uzlabotu jÅ«su konfigurāciju. Piemēram, mēs varam izmantot
    iezÄ«mes noklusējuma vērtÄ«bām, izmantojot objektus, lai grupētu parametrus, mēs varam atsaukties uz vērtÄ«bām, kas deklarētas tikai vienu reizi (DRY) aptveroÅ”ajā tvērumā. JÅ«s varat izveidot jebkuras klases tieÅ”i konfigurācijā (Seq, Map, pielāgotas nodarbÄ«bas).
  3. DSL. Scala ir vairākas valodas funkcijas, kas atvieglo DSL izveidi. Ir iespējams izmantot Ŕīs iespējas un ieviest lietotāju mērÄ·a grupai ērtāku konfigurācijas valodu, lai konfigurācija bÅ«tu vismaz salasāma domēna ekspertiem. Speciālisti var, piemēram, piedalÄ«ties konfigurācijas pārskatÄ«Å”anas procesā.
  4. Integritāte un sinhronitāte starp mezgliem. Viena no priekÅ”rocÄ«bām, ja visas sadalÄ«tās sistēmas konfigurācija tiek glabāta vienā punktā, ir tā, ka visas vērtÄ«bas tiek deklarētas tieÅ”i vienreiz un pēc tam tiek izmantotas atkārtoti, kur vien tās ir vajadzÄ«gas. Fantoma tipu izmantoÅ”ana portu deklarÄ“Å”anai nodroÅ”ina, ka mezgli izmanto saderÄ«gus protokolus visās pareizajās sistēmas konfigurācijās. Ja starp mezgliem ir noteiktas obligātās atkarÄ«bas, tiek nodroÅ”ināts, ka visi pakalpojumi ir savienoti.
  5. Augstas kvalitātes izmaiņas. Izmaiņu veikÅ”ana konfigurācijā, izmantojot vienotu izstrādes procesu, ļauj sasniegt augstus kvalitātes standartus arÄ« konfigurācijai.
  6. VienlaicÄ«ga konfigurācijas atjaunināŔana. Automātiska sistēmas izvietoÅ”ana pēc konfigurācijas izmaiņām nodroÅ”ina visu mezglu atjaunināŔanu.
  7. Lietojumprogrammas vienkārÅ”oÅ”ana. Lietojumprogrammai nav nepiecieÅ”ama parsÄ“Å”ana, konfigurācijas pārbaude vai nepareizu vērtÄ«bu apstrāde. Tas samazina lietojumprogrammas sarežģītÄ«bu. (Daļa no mÅ«su piemērā novērotās konfigurācijas sarežģītÄ«bas nav kompilētās konfigurācijas atribÅ«ts, bet gan tikai apzināts lēmums, ko virza vēlme nodroÅ”ināt lielāku tipa droŔību.) Atgriezties pie ierastās konfigurācijas ir diezgan vienkārÅ”i ā€“ vienkārÅ”i ieviesiet trÅ«kstoÅ”o. daļas. Tāpēc jÅ«s varat, piemēram, sākt ar apkopotu konfigurāciju, atliekot nevajadzÄ«go daļu ievieÅ”anu lÄ«dz brÄ«dim, kad tā patieŔām bÅ«s nepiecieÅ”ama.
  8. Versificēta konfigurācija. Tā kā konfigurācijas izmaiņas seko parastajam jebkuru citu izmaiņu liktenim, iegÅ«tais rezultāts ir artefakts ar unikālu versiju. Tas ļauj mums, piemēram, vajadzÄ«bas gadÄ«jumā atgriezties pie iepriekŔējās konfigurācijas versijas. Mēs pat varam izmantot konfigurāciju pirms gada, un sistēma darbosies tieÅ”i tāpat. Stabila konfigurācija uzlabo sadalÄ«tās sistēmas paredzamÄ«bu un uzticamÄ«bu. Tā kā konfigurācija tiek fiksēta kompilācijas stadijā, ražoÅ”anā to ir diezgan grÅ«ti viltot.
  9. Modularitāte. Piedāvātais ietvars ir modulārs, un moduļus var kombinēt dažādos veidos, lai izveidotu dažādas sistēmas. Jo Ä«paÅ”i varat konfigurēt sistēmu, lai tā darbotos vienā mezglā vienā iemiesojumā un vairākos mezglos citā. Sistēmas ražoÅ”anas gadÄ«jumiem varat izveidot vairākas konfigurācijas.
  10. TestÄ“Å”ana. Aizvietojot atseviŔķus pakalpojumus ar imitācijas objektiem, jÅ«s varat iegÅ«t vairākas testÄ“Å”anai ērtas sistēmas versijas.
  11. Integrācijas pārbaude. Viena konfigurācija visai izplatÄ«tajai sistēmai ļauj darbināt visus komponentus kontrolētā vidē kā daļu no integrācijas testÄ“Å”anas. Ir viegli lÄ«dzināties, piemēram, situācijai, kad daži mezgli kļūst pieejami.

Trūkumi un ierobežojumi

Kompilētā konfigurācija atŔķiras no citām konfigurācijas pieejām un var nebÅ«t piemērota dažām lietojumprogrammām. Zemāk ir daži trÅ«kumi:

  1. Statiskā konfigurācija. Dažreiz jums ir ātri jālabo konfigurācija ražoŔanā, apejot visus aizsargmehānismus. Ar Ŕo pieeju tas var būt grūtāk. Vismaz joprojām būs nepiecieŔama kompilācija un automātiska izvietoŔana. Tā ir gan noderīga pieejas iezīme, gan dažos gadījumos trūkums.
  2. Konfigurācijas Ä£enerÄ“Å”ana. Ja konfigurācijas failu Ä£enerē automātisks rÄ«ks, var bÅ«t nepiecieÅ”amas papildu pÅ«les, lai integrētu bÅ«vÄ“Å”anas skriptu.
  3. RÄ«ki. PaÅ”laik utilÄ«tas un metodes, kas paredzētas darbam ar konfigurāciju, ir balstÄ«tas uz teksta failiem. Ne visas Ŕādas utilÄ«tas/tehnikas bÅ«s pieejamas apkopotā konfigurācijā.
  4. NepiecieÅ”ama attieksmes maiņa. Izstrādātāji un DevOps ir pieraduÅ”i pie teksta failiem. Pati ideja par konfigurācijas sastādÄ«Å”anu var bÅ«t nedaudz negaidÄ«ta un neparasta un izraisÄ«t noraidÄ«jumu.
  5. NepiecieÅ”ams augstas kvalitātes izstrādes process. Lai ērti izmantotu apkopoto konfigurāciju, ir nepiecieÅ”ama pilna lietojumprogrammas (CI/CD) izveides un izvietoÅ”anas procesa automatizācija. Pretējā gadÄ«jumā tas bÅ«s diezgan neērti.

Pakavēsimies arī pie vairākiem aplūkotā piemēra ierobežojumiem, kas nav saistīti ar kompilētas konfigurācijas ideju:

  1. Ja mēs sniedzam nevajadzÄ«gu konfigurācijas informāciju, kuru mezgls neizmanto, kompilators mums nepalÄ«dzēs noteikt trÅ«kstoÅ”o implementāciju. Å o problēmu var atrisināt, atsakoties no kÅ«ku raksta un izmantojot stingrākus veidus, piemēram, HList vai algebrisko datu tipi (gadÄ«jumu klases), lai attēlotu konfigurāciju.
  2. Konfigurācijas failā ir rindas, kas nav saistÄ«tas ar paÅ”u konfigurāciju: (package, import,objektu deklarācijas; override def's parametriem, kuriem ir noklusējuma vērtÄ«bas). To var daļēji izvairÄ«ties, ja ievieÅ”at savu DSL. Turklāt citi konfigurācijas veidi (piemēram, XML) arÄ« uzliek noteiktus ierobežojumus faila struktÅ«rai.
  3. Šīs ziņas vajadzībām mēs neapsveram līdzīgu mezglu klastera dinamisku pārkonfigurāciju.

Secinājums

Å ajā ziņojumā mēs izpētÄ«jām ideju par konfigurācijas attēloÅ”anu avota kodā, izmantojot Scala tipa sistēmas uzlabotās iespējas. Å o pieeju var izmantot dažādās lietojumprogrammās, lai aizstātu tradicionālās konfigurācijas metodes, kuru pamatā ir xml vai teksta faili. Pat ja mÅ«su piemērs ir ieviests Scala, tās paÅ”as idejas var pārnest uz citām apkopotajām valodām (piemēram, Kotlin, C#, Swift, ...). Varat izmēģināt Å”o pieeju kādā no Å”iem projektiem un, ja tā nedarbojas, pārejiet uz teksta failu, pievienojot trÅ«kstoŔās daļas.

Protams, kompilētai konfigurācijai ir nepiecieÅ”ams augstas kvalitātes izstrādes process. PretÄ« tiek nodroÅ”ināta augsta konfigurāciju kvalitāte un uzticamÄ«ba.

Apsvērto pieeju var paplaÅ”ināt:

  1. Varat izmantot makro, lai veiktu kompilÄ“Å”anas laika pārbaudes.
  2. Varat ieviest DSL, lai parādītu konfigurāciju galalietotājiem pieejamā veidā.
  3. Varat ieviest dinamisku resursu pārvaldÄ«bu ar automātisku konfigurācijas pielāgoÅ”anu. Piemēram, mainot mezglu skaitu klasterÄ«, ir nepiecieÅ”ams, lai (1) katrs mezgls saņemtu nedaudz atŔķirÄ«gu konfigurāciju; (2) klastera pārvaldnieks saņēma informāciju par jauniem mezgliem.

Pateicības

Vēlos pateikties Andrejam Saksonovam, Pāvelam Popovam un Antonam Ņehajevam par konstruktīvo kritiku par raksta projektu.

Avots: www.habr.com

Pievieno komentāru