Kompilebla agordo de distribuita sistemo

En ĉi tiu afiŝo ni ŝatus dividi interesan manieron trakti agordon de distribuita sistemo.
La agordo estas reprezentita rekte en Scala lingvo en tipo sekura maniero. Ekzempla efektivigo estas priskribita en detaloj. Diversaj aspektoj de la propono estas diskutitaj, inkluzive de influo sur la ĝenerala evoluprocezo.

Kompilebla agordo de distribuita sistemo

(en la rusa)

Enkonduko

Konstrui fortikan distribuitajn sistemojn postulas la uzon de ĝusta kaj kohera agordo sur ĉiuj nodoj. Tipa solvo estas uzi tekstan deplojan priskribon (terraform, ansible aŭ io simila) kaj aŭtomate generitajn agordajn dosierojn (ofte — dediĉitaj por ĉiu nodo/rolo). Ni ankaŭ volus uzi la samajn protokolojn de la samaj versioj sur ĉiu komunika nodo (alie ni spertus nekongruecajn problemojn). En JVM-mondo tio signifas, ke almenaŭ la mesaĝa biblioteko devus esti de la sama versio sur ĉiuj komunikaj nodoj.

Kio pri testado de la sistemo? Kompreneble, ni devus havi unutestojn por ĉiuj komponantoj antaŭ ol veni al integrigaj testoj. Por povi eksterpoli testrezultojn en rultempo, ni devus certigi ke la versioj de ĉiuj bibliotekoj estas konservitaj identaj en ambaŭ rultempo kaj testaj medioj.

Dum plenumado de integrigaj testoj, estas ofte multe pli facile havi la saman klasvojon sur ĉiuj nodoj. Ni nur bezonas certigi, ke la sama klaspado estas uzata dum deplojo. (Eblas uzi malsamajn klasvojojn sur malsamaj nodoj, sed estas pli malfacile reprezenti ĉi tiun agordon kaj ĝuste deploji ĝin.) Do por konservi aferojn simplaj ni nur konsideros identajn klasvojojn sur ĉiuj nodoj.

Agordo tendencas evolui kune kun la programaro. Ni kutime uzas versiojn por identigi diversajn
etapoj de evoluado de programaro. Ŝajnas racie kovri agordon sub versio-administrado kaj identigi malsamajn agordojn per kelkaj etikedoj. Se estas nur unu agordo en produktado, ni povas uzi ununuran version kiel identigilon. Kelkfoje ni povas havi plurajn produktadmediojn. Kaj por ĉiu medio ni eble bezonos apartan branĉon de agordo. Do agordoj povus esti etikeditaj kun branĉo kaj versio por unike identigi malsamajn agordojn. Ĉiu branĉo-etikedo kaj versio egalrilatas al ununura kombinaĵo de distribuitaj nodoj, havenoj, eksteraj resursoj, klasvojaj bibliotekversioj sur ĉiu nodo. Ĉi tie ni nur kovros la ununuran branĉon kaj identigos agordojn per trikompona dekuma versio (1.2.3), same kiel aliaj artefaktoj.

En modernaj medioj agordaj dosieroj ne plu estas modifitaj permane. Tipe ni generas
agordaj dosieroj en la tempo de deplojo kaj neniam tuŝu ilin poste. Do oni povus demandi kial ni ankoraŭ uzas tekstformaton por agordaj dosieroj? Realigebla elekto estas meti la agordon ene de kompilunuo kaj profiti el kompiltempa konfiguraciovalidigo.

En ĉi tiu afiŝo ni ekzamenos la ideon konservi la agordon en la kompilita artefakto.

Kompilebla agordo

En ĉi tiu sekcio ni diskutos ekzemplon de statika agordo. Du simplaj servoj - eĥservo kaj la kliento de la eĥservo estas agorditaj kaj efektivigitaj. Tiam du malsamaj distribuitaj sistemoj kun ambaŭ servoj estas instanciigitaj. Unu estas por ununura noda agordo kaj alia por du noda agordo.

Tipa distribuita sistemo konsistas el kelkaj nodoj. La nodoj povus esti identigitaj uzante iun tipon:

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

aŭ nur

case class NodeId(hostName: String)

aŭ eĉ

object Singleton
type NodeId = Singleton.type

Ĉi tiuj nodoj plenumas diversajn rolojn, funkcias iujn servojn kaj devus povi komuniki kun la aliaj nodoj per TCP/HTTP-konektoj.

Por TCP-konekto almenaŭ havenda numero estas bezonata. Ni ankaŭ volas certigi, ke kliento kaj servilo parolas la saman protokolon. Por modeligi konekton inter nodoj ni deklaru la sekvan klason:

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

kie Port estas nur an Int ene de la permesita intervalo:

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

Rafinitaj tipoj

Vidu rafinita biblioteko. Mallonge, ĝi permesas aldoni kompilajn tempolimojn al aliaj tipoj. Tiuokaze Int nur rajtas havi 16-bitajn valorojn, kiuj povas reprezenti havenon. Ne estas postulo uzi ĉi tiun bibliotekon por ĉi tiu agorda aliro. Ĝi nur ŝajnas konveni tre bone.

Por HTTP (REST) ​​​​ni eble ankaŭ bezonos vojon de la servo:

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

Fantoma tipo

Por identigi protokolon dum kompilo ni uzas la Scala funkcion de deklarado de tipo-argumento Protocol tio ne estas uzata en la klaso. Ĝi estas tiel nomata fantoma tipo. Ĉe rultempo ni malofte bezonas ekzemplon de protokolo-identigilo, tial ni ne konservas ĝin. Dum kompilo tiu fantoma tipo donas plian tipsekurecon. Ni ne povas pasi havenon kun malĝusta protokolo.

Unu el la plej uzataj protokoloj estas REST API kun seriigo Json:

sealed trait JsonHttpRestProtocol[RequestMessage, ResponseMessage]

kie RequestMessage estas la baza tipo de mesaĝoj kiujn kliento povas sendi al servilo kaj ResponseMessage estas la respondmesaĝo de servilo. Kompreneble, ni povas krei aliajn protokolajn priskribojn, kiuj precizigas la komunikan protokolon kun la dezirata precizeco.

Por la celoj de ĉi tiu afiŝo ni uzos pli simplan version de la protokolo:

sealed trait SimpleHttpGetRest[RequestMessage, ResponseMessage]

En ĉi tiu protokolo petomesaĝo estas almetita al url kaj respondmesaĝo estas resendita kiel simpla ĉeno.

Serva agordo povus esti priskribita per la servonomo, kolekto de havenoj kaj iuj dependecoj. Estas kelkaj eblaj manieroj kiel reprezenti ĉiujn ĉi tiujn elementojn en Scala (ekzemple, HList, algebraj datumtipoj). Por la celoj de ĉi tiu afiŝo ni uzos Kukan Ŝablonon kaj reprezentos kombineblajn pecojn (modulojn) kiel trajtojn. (Kuka Ŝablono ne estas postulo por ĉi tiu kompilebla agorda aliro. Ĝi estas nur unu ebla efektivigo de la ideo.)

Dependecoj povus esti reprezentitaj uzante la Kukon kiel finpunktojn de aliaj nodoj:

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

Eĥa servo bezonas nur havenon agordita. Kaj ni deklaras, ke ĉi tiu haveno subtenas eĥan protokolon. Notu, ke ni ne bezonas specifi apartan havenon en ĉi tiu momento, ĉar trajtoj permesas abstraktajn metodojn deklarojn. Se ni uzas abstraktajn metodojn, kompililo postulos efektivigon en agorda kazo. Ĉi tie ni disponigis la efektivigon (8081) kaj ĝi estos uzata kiel defaŭlta valoro se ni transsaltas ĝin en konkreta agordo.

Ni povas deklari dependecon en la agordo de la eĥserva kliento:

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

Dependeco havas la saman tipon kiel la echoService. Precipe ĝi postulas la saman protokolon. Tial ni povas esti certaj, ke se ni konektas ĉi tiujn du dependencojn, ili funkcios ĝuste.

Servoj efektivigo

Servo bezonas funkcion por komenci kaj gracie malŝalti. (Kapablo ĉesigi servon estas kritika por testado.) Denove ekzistas kelkaj ebloj por specifi tian funkcion por donita agordo (ekzemple, ni povus uzi tipklasojn). Por ĉi tiu afiŝo ni uzos Kukan Ŝablonon denove. Ni povas reprezenti servon uzante cats.Resource kiu jam disponigas krampadon kaj rimedon liberigon. Por akiri rimedon ni devus provizi agordon kaj iom da rultempa kunteksto. Do la servo komenca funkcio povus aspekti kiel:

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

kie

  • Config — tipo de agordo, kiu estas postulata de ĉi tiu servo-komencilo
  • AddressResolver — rultempa objekto, kiu havas la kapablon akiri realajn adresojn de aliaj nodoj (daŭre legu por detaloj).

la aliaj tipoj venas de cats:

  • F[_] — efektotipo (En la plej simpla kazo F[A] povus esti justa () => A. En ĉi tiu afiŝo ni uzos cats.IO.)
  • Reader[A,B] — estas pli-malpli sinonimo de funkcio A => B
  • cats.Resource — havas manierojn akiri kaj liberigi
  • Timer — permesas dormi/mezuri tempon
  • ContextShift - analogo de ExecutionContext
  • Applicative — envolvaĵo de funkcioj efektive (preskaŭ monado) (ni eble anstataŭigos ĝin per io alia)

Uzante ĉi tiun interfacon ni povas efektivigi kelkajn servojn. Ekzemple, servo kiu faras nenion:

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

(Vidu Fonta kodo por aliaj servoj-efektivigoj - eĥservo,
eĥa kliento kaj dumvivaj regiloj.)

Nodo estas ununura objekto, kiu funkciigas kelkajn servojn (komenco de ĉeno de rimedoj estas ebligita de Cake Pattern):

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

Notu, ke en la nodo ni specifas la ĝustan tipon de agordo, kiun bezonas ĉi tiu nodo. Kompililo ne lasos nin konstrui la objekton (Kukon) kun nesufiĉa tipo, ĉar ĉiu serva trajto deklaras limon sur la Config tajpu. Ankaŭ ni ne povos komenci nodon sen provizi kompletan agordon.

Noda adreso rezolucio

Por establi konekton ni bezonas veran gastigandreson por ĉiu nodo. Ĝi eble estos konata poste ol aliaj partoj de la agordo. Tial ni bezonas manieron provizi mapadon inter noda id kaj ĝia fakta adreso. Ĉi tiu mapado estas funkcio:

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

Estas kelkaj eblaj manieroj efektivigi tian funkcion.

  1. Se ni konas realajn adresojn antaŭ deplojo, dum nodo gastigas instantiation, tiam ni povas generi Scala kodon kun la realaj adresoj kaj ruli la konstruon poste (kiu elfaras kompiltempo kontroloj kaj tiam rulas integrigan testan suiteon). En ĉi tiu kazo nia mapa funkcio estas konata statike kaj povas esti simpligita al io kiel a Map[NodeId, NodeAddress].
  2. Foje ni akiras realajn adresojn nur en pli posta punkto kiam la nodo estas efektive komencita, aŭ ni ne havas adresojn de nodoj kiuj ankoraŭ ne estis komencitaj. En ĉi tiu kazo ni eble havas malkovran servon, kiu estas komencita antaŭ ĉiuj aliaj nodoj kaj ĉiu nodo povus reklami sian adreson en tiu servo kaj aboni dependecojn.
  3. Se ni povas modifi /etc/hosts, ni povas uzi antaŭdifinitajn gastigajn nomojn (kiel my-project-main-node kaj echo-backend) kaj simple asocii ĉi tiun nomon kun ip-adreso ĉe la tempo de deplojo.

En ĉi tiu afiŝo ni ne kovras ĉi tiujn kazojn pli detale. Fakte en nia ludila ekzemplo ĉiuj nodoj havos la saman IP-adreson — 127.0.0.1.

En ĉi tiu afiŝo ni konsideros du distribuitajn sistemajn aranĝojn:

  1. Ununura noda aranĝo, kie ĉiuj servoj estas metitaj sur la ununuran nodon.
  2. Du-noda aranĝo, kie servo kaj kliento estas sur malsamaj nodoj.

La agordo por a ununura nodo aranĝo estas kiel sekvas:

Ununoda agordo

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

Ĉi tie ni kreas ununuran agordon kiu etendas kaj servilon kaj klientan agordon. Ankaŭ ni agordas vivciklan regilon, kiu kutime finos klienton kaj servilon poste lifetime intervalo pasas.

La sama aro de servo efektivigoj kaj konfiguracioj povas esti uzata por krei sistemon enpaĝigo kun du apartaj nodoj. Ni nur bezonas krei du apartaj nodaj agordoj kun la taŭgaj servoj:

Agordo de du nodoj

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

Vidu kiel ni specifas la dependecon. Ni mencias la provizitan servon de la alia nodo kiel dependecon de la nuna nodo. La speco de dependeco estas kontrolita ĉar ĝi enhavas fantoman tipon kiu priskribas protokolon. Kaj ĉe rultempo ni havos la ĝustan nodan id. Ĉi tio estas unu el la gravaj aspektoj de la proponita agorda aliro. Ĝi donas al ni la kapablon agordi havenon nur unufoje kaj certigi, ke ni referencas la ĝustan havenon.

Du nodoj efektivigo

Por ĉi tiu agordo ni uzas precize la samajn servojn efektivigojn. Tute neniuj ŝanĝoj. Tamen, ni kreas du malsamajn nodajn efektivigojn, kiuj enhavas malsaman aron da servoj:

  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
  }

La unua nodo efektivigas servilon kaj ĝi nur bezonas servilflankan agordon. La dua nodo efektivigas klienton kaj bezonas alian parton de agordo. Ambaŭ nodoj postulas iun dumvivan specifon. Por la celoj de ĉi tiu poŝta servo nodo havos senfinan vivdaŭron kiu povus esti finita uzante SIGTERM, dum eĥo-kliento finiĝos post la agordita finhava daŭro. Vidu la komenca aplikaĵo por detaloj.

Ĝenerale disvolva procezo

Ni vidu kiel ĉi tiu aliro ŝanĝas la manieron kiel ni laboras kun agordo.

La agordo kiel kodo estos kompilita kaj produktas artefakton. Ŝajnas racie apartigi agordan artefakton de aliaj kodaj artefaktoj. Ofte ni povas havi amason da agordoj sur la sama kodbazo. Kaj kompreneble, ni povas havi plurajn versiojn de diversaj agordaj branĉoj. En agordo ni povas elekti apartajn versiojn de bibliotekoj kaj ĉi tio restos konstanta kiam ajn ni deplojos ĉi tiun agordon.

Ŝanĝo de agordo iĝas kodŝanĝo. Do ĝi devus esti kovrita per la sama kvalita certiga procezo:

Bileto -> PR -> revizio -> kunfandi -> kontinua integriĝo -> kontinua deplojo

Estas la sekvaj konsekvencoj de la aliro:

  1. La agordo estas kohera por la kazo de aparta sistemo. Ŝajnas, ke ne estas maniero havi malĝustan konekton inter nodoj.
  2. Ne estas facile ŝanĝi agordon nur en unu nodo. Ŝajnas neracie ensaluti kaj ŝanĝi kelkajn tekstajn dosierojn. Do agorda drivo fariĝas malpli ebla.
  3. Malgrandaj agordaj ŝanĝoj ne estas facile fareblaj.
  4. La plej multaj el la agordaj ŝanĝoj sekvos la saman disvolvan procezon, kaj ĝi trapasos iom da revizio.

Ĉu ni bezonas apartan deponejon por produktada agordo? La produktada agordo povus enhavi sentemajn informojn, kiujn ni ŝatus konservi ekster la atingo de multaj homoj. Do eble indas konservi apartan deponejon kun limigita aliro, kiu enhavos la produktadkonfiguracion. Ni povas dividi la agordon en du partojn - unu kiu enhavas la plej malfermajn parametrojn de produktado kaj unu kiu enhavas la sekretan parton de agordo. Ĉi tio ebligus aliron al la plej multaj el la programistoj al la vasta plimulto de parametroj dum limigas aliron al vere sentemaj aferoj. Estas facile plenumi ĉi tion uzante mezajn trajtojn kun defaŭltaj parametraj valoroj.

Variadoj

Ni vidu avantaĝojn kaj malavantaĝojn de la proponita aliro kompare kun la aliaj agordaj administradaj teknikoj.

Antaŭ ĉio, ni listigos kelkajn alternativojn al la malsamaj aspektoj de la proponita maniero trakti agordon:

  1. Teksto dosiero sur la cela maŝino.
  2. Alcentrigita ŝlosilvalora stokado (kiel etcd/zookeeper).
  3. Subprocezaj komponantoj, kiuj povus esti reagorditaj/rekomencitaj sen rekomenci procezon.
  4. Agordo ekster artefakto kaj versio-kontrolo.

Tekstodosiero donas iom da fleksebleco laŭ ad-hoc korektoj. Administranto de sistemo povas ensaluti al la cela nodo, fari ŝanĝon kaj simple rekomenci la servon. Ĉi tio eble ne estas tiel bona por pli grandaj sistemoj. Neniuj spuroj restas malantaŭ la ŝanĝo. La ŝanĝo ne estas reviziita de alia paro de okuloj. Eble estos malfacile ekscii, kio kaŭzis la ŝanĝon. Ĝi ne estis provita. De la perspektivo de distribuita sistemo administranto povas simple forgesi ĝisdatigi la agordon en unu el la aliaj nodoj.

(Btw, se eventuale estos bezono komenci uzi tekstajn agordosierojn, ni nur devos aldoni analizilon + validigilon, kiuj povus produkti la saman Config tajpu kaj tio sufiĉus por ekuzi tekstajn agordojn. Ĉi tio ankaŭ montras, ke la komplekseco de kompiltempa agordo estas iom pli malgranda ol la komplekseco de tekst-bazitaj agordoj, ĉar en tekst-bazita versio ni bezonas iun kroman kodon.)

Alcentrigita ŝlosilvalora stokado estas bona mekanismo por distribuado de aplikaj meta-parametroj. Ĉi tie ni devas pensi pri tio, kion ni konsideras agordaj valoroj kaj kio estas nur datumoj. Donita funkcio C => A => B ni kutime nomas malofte ŝanĝiĝantaj valoroj C "agordo", dum ofte ŝanĝitaj datumoj A - nur enigu datumojn. Agordo devus esti provizita al la funkcio pli frue ol la datumoj A. Konsiderante ĉi tiun ideon, ni povas diri, ke estas atendita ofteco de ŝanĝoj, kio povus esti uzata por distingi agordajn datumojn de nuraj datumoj. Ankaŭ datumoj kutime venas de unu fonto (uzanto) kaj agordo venas de malsama fonto (administranto). Trakti parametrojn kiuj povas esti ŝanĝitaj post la inicialigprocezo kondukas al pliigo de aplika komplekseco. Por tiaj parametroj ni devos pritrakti ilian liveran mekanismon, analizadon kaj validigon, pritraktante malĝustajn valorojn. Tial, por redukti programkompleksecon, ni prefere reduktu la nombron da parametroj kiuj povas ŝanĝiĝi ĉe rultempo (aŭ eĉ forigi ilin entute).

El la perspektivo de ĉi tiu afiŝo ni devus fari distingon inter statikaj kaj dinamikaj parametroj. Se serva logiko postulas maloftan ŝanĝon de iuj parametroj ĉe rultempo, tiam ni povas nomi ilin dinamikaj parametroj. Alie ili estas senmovaj kaj povus esti agorditaj uzante la proponitan aliron. Por dinamika reagordo povus esti bezonataj aliaj aliroj. Ekzemple, partoj de la sistemo eble estos rekomencitaj kun la novaj agordaj parametroj en simila maniero al rekomencado de apartaj procezoj de distribuita sistemo.
(Mia humila opinio estas eviti rultempan reagordon ĉar ĝi pliigas kompleksecon de la sistemo.
Eble estus pli simple nur fidi je OS-subteno por rekomenci procezojn. Tamen, eble ne ĉiam eblas.)

Unu grava aspekto de uzado de senmova agordo, kiu foje igas homojn pripensi dinamikan agordon (sen aliaj kialoj) estas servo malfunkcio dum agorda ĝisdatigo. Efektive, se ni devas fari ŝanĝojn al statika agordo, ni devas rekomenci la sistemon por ke novaj valoroj fariĝu efikaj. La postuloj por malfunkcio varias laŭ malsamaj sistemoj, do ĝi eble ne estas tiel kritika. Se ĝi estas kritika, tiam ni devas antaŭplani por ajna sistemo rekomencas. Ekzemple, ni povus efektivigi AWS ELB-konekto drenado. En ĉi tiu scenaro, kiam ajn ni bezonas rekomenci la sistemon, ni komencas novan ekzemplon de la sistemo paralele, tiam ŝanĝas ELB al ĝi, dum lasante la malnovan sistemon kompletigi la servadon de ekzistantaj ligoj.

Kio pri konservado de agordo ene de versionita artefakto aŭ ekstere? Konservi agordon ene de artefakto signifas en la plej multaj el la kazoj, ke ĉi tiu agordo trapasis la saman kvalitcertigan procezon kiel aliaj artefaktoj. Do oni povus esti certa, ke la agordo estas bonkvalita kaj fidinda. Male agordo en aparta dosiero signifas, ke ne estas spuroj de kiu kaj kial faris ŝanĝojn al tiu dosiero. Ĉu ĉi tio estas grava? Ni kredas, ke por plej multaj produktadsistemoj estas pli bone havi stabilan kaj altkvalitan agordon.

Versio de la artefakto permesas ekscii kiam ĝi estis kreita, kiajn valorojn ĝi enhavas, kiajn funkciojn estas aktivigitaj/malŝaltitaj, kiu respondecis pri fari ĉiun ŝanĝon en la agordo. Eble necesas iom da penado konservi agordon ene de artefakto kaj ĝi estas dezajna elekto por fari.

Avantaĝoj kaj malavantaĝoj

Ĉi tie ni ŝatus reliefigi kelkajn avantaĝojn kaj diskuti kelkajn malavantaĝojn de la proponita aliro.

Avantaĝoj

Trajtoj de la kompilebla agordo de kompleta distribuita sistemo:

  1. Statika kontrolo de agordo. Ĉi tio donas altnivelan de fido, ke la agordo estas ĝusta donitaj tiplimoj.
  2. Riĉa lingvo de agordo. Tipe aliaj agordaliroj estas limigitaj al maksimume varia anstataŭigo.
    Uzante Scala oni povas uzi ampleksan gamon de lingvotrajtoj por plibonigi agordon. Ekzemple, ni povas uzi trajtojn por provizi defaŭltajn valorojn, objektojn por agordi malsaman amplekson, al kiuj ni povas raporti vals difinita nur unufoje en la ekstera amplekso (SEKA). Eblas uzi laŭvortajn sekvencojn, aŭ okazojn de certaj klasoj (Seq, Map, ktp).
  3. DSL. Scala havas decan subtenon por DSL-verkistoj. Oni povas uzi ĉi tiujn funkciojn por establi agordan lingvon kiu estas pli oportuna kaj afabla por finuzantoj, tiel ke la fina agordo estas almenaŭ legebla de domajnaj uzantoj.
  4. Integreco kaj kohereco trans nodoj. Unu el la avantaĝoj de havi agordon por la tuta distribuita sistemo en unu loko estas, ke ĉiuj valoroj estas difinitaj strikte unufoje kaj poste reuzitaj en ĉiuj lokoj, kie ni bezonas ilin. Ankaŭ tajpu sekurajn havendeklarojn certigu, ke en ĉiuj eblaj ĝustaj agordoj la nodoj de la sistemo parolos la saman lingvon. Estas eksplicitaj dependecoj inter nodoj, kio malfaciligas forgesi provizi iujn servojn.
  5. Alta kvalito de ŝanĝoj. La ĝenerala aliro de pasi agordajn ŝanĝojn tra normala PR-procezo establas altajn normojn de kvalito ankaŭ en agordo.
  6. Samtempaj agordaj ŝanĝoj. Kiam ajn ni faras ajnajn ŝanĝojn en la agordo, aŭtomata deplojo certigas, ke ĉiuj nodoj estas ĝisdatigitaj.
  7. Simpligo de aplikaĵo. La aplikaĵo ne bezonas analizi kaj validigi agordojn kaj trakti malĝustajn agordajn valorojn. Ĉi tio simpligas la ĝeneralan aplikon. (Iu kompleksecpliiĝo estas en la agordo mem, sed ĝi estas konscia kompromiso al sekureco.) Estas sufiĉe simple reveni al ordinara agordo — nur aldonu la mankantajn pecojn. Estas pli facile komenci kun kompilita agordo kaj prokrasti efektivigon de pliaj pecoj al kelkaj pli postaj tempoj.
  8. Versia agordo. Pro la fakto, ke agordaj ŝanĝoj sekvas la saman disvolvan procezon, kiel rezulto ni ricevas artefakton kun unika versio. Ĝi permesas al ni ŝanĝi agordon reen se necese. Ni eĉ povas deploji agordon kiu estis uzita antaŭ jaro kaj ĝi funkcios ĝuste same. Stabila agordo plibonigas antaŭvideblecon kaj fidindecon de la distribuita sistemo. La agordo estas fiksita je kompiltempo kaj ne povas esti facile mistraktita sur produktadsistemo.
  9. Modulareco. La proponita kadro estas modula kaj moduloj povus esti kombinitaj en diversaj manieroj
    subtenu malsamajn agordojn (aranĝoj/aranĝoj). Aparte, eblas havi malgrandskalan ununodan aranĝon kaj grandskalan multnodan agordon. Estas racie havi plurajn produktadajn aranĝojn.
  10. Testado. Por testaj celoj oni povus efektivigi imitan servon kaj uzi ĝin kiel dependecon en sekura maniero. Kelkaj malsamaj testaj aranĝoj kun diversaj partoj anstataŭigitaj per mokoj povus esti konservitaj samtempe.
  11. Testo de integriĝo. Foje en distribuitaj sistemoj estas malfacile ruli integrigajn testojn. Uzante la priskribitan aliron por tajpi sekuran agordon de la kompleta distribuita sistemo, ni povas ruli ĉiujn distribuitajn partojn sur ununura servilo en regebla maniero. Estas facile imiti la situacion
    kiam unu el la servoj fariĝas neatingebla.

malavantaĝoj

La kompilita agorda aliro estas diferenca de "normala" agordo kaj ĝi eble ne konvenas al ĉiuj bezonoj. Jen kelkaj el la malavantaĝoj de la kompilita agordo:

  1. Statika agordo. Ĝi eble ne taŭgas por ĉiuj aplikoj. En iuj kazoj necesas rapide ripari la agordon en produktado preterpasante ĉiujn sekurecajn mezurojn. Ĉi tiu aliro faras ĝin pli malfacila. La kompilo kaj redeplojo estas postulataj post fari ajnan ŝanĝon en agordo. Ĉi tio estas kaj la trajto kaj la ŝarĝo.
  2. Generacio de agordo. Kiam konfig estas generita de iu aŭtomatiga ilo, ĉi tiu aliro postulas postan kompilon (kiu eble malsukcesos). Eble postulas plian penon integri ĉi tiun kroman paŝon en la konstrusistemon.
  3. Instrumentoj. Estas multaj iloj uzataj hodiaŭ, kiuj dependas de tekst-bazitaj agordoj. Kelkaj el ili
    ne estos aplikebla kiam agordo estas kompilita.
  4. Necesas ŝanĝo de pensmaniero. Programistoj kaj DevOps konas tekstajn agordajn dosierojn. La ideo de kompili agordon povus ŝajni stranga al ili.
  5. Antaŭ enkonduko de kompilebla agordo, necesas altkvalita programaro disvolva procezo.

Estas kelkaj limigoj de la efektivigita ekzemplo:

  1. Se ni provizas kroman agordon, kiu ne estas postulata de la noda efektivigo, kompililo ne helpos nin detekti la forestantan efektivigon. Ĉi tio povus esti traktita uzante HList aŭ ADToj (kazklasoj) por noda agordo anstataŭ trajtoj kaj Kuka Ŝablono.
  2. Ni devas provizi iun kaldronon en agorda dosiero: (package, import, object deklaroj;
    override def's por parametroj kiuj havas defaŭltajn valorojn). Ĉi tio povus esti parte traktita per DSL.
  3. En ĉi tiu afiŝo ni ne kovras dinamikan reagordon de aretoj de similaj nodoj.

konkludo

En ĉi tiu afiŝo ni diskutis la ideon reprezenti agordon rekte en la fontkodo en sekura maniero. La aliro povus esti uzata en multaj aplikoj kiel anstataŭaĵo al xml- kaj aliaj tekst-bazitaj agordoj. Malgraŭ ke nia ekzemplo estis efektivigita en Scala, ĝi ankaŭ povus esti tradukita al aliaj kompileblaj lingvoj (kiel Kotlin, C#, Swift, ktp.). Oni povus provi ĉi tiun aliron en nova projekto kaj, se ĝi ne bone taŭgas, ŝanĝi al la malnova maniero.

Kompreneble, kompilebla agordo postulas altkvalitan disvolvan procezon. Kontraŭe ĝi promesas disponigi same altkvalitan fortikan agordon.

Ĉi tiu aliro povus esti etendita laŭ diversaj manieroj:

  1. Oni povus uzi makroojn por plenumi agordan validigon kaj malsukcesi je kompilotempo en kazo de iu ajn komerca logika limigo fiaskoj.
  2. DSL povus esti efektivigita por reprezenti agordon en domajna uzant-amika maniero.
  3. Dinamika administrado de rimedoj kun aŭtomataj agordaj ĝustigoj. Ekzemple, kiam ni ĝustigas la nombron da aretnodoj ni eble volas (1) la nodoj akiri iomete modifitan konfiguracion; (2) clustermanaĝero por ricevi novajn nodojn informojn.

danke

Mi ŝatus danki vin al Andrey Saksonov, Pavel Popov, Anton Nehaev por doni inspirajn rimarkojn pri la skizo de ĉi tiu afiŝo, kiu helpis min pliklarigi ĝin.

fonto: www.habr.com