Samhæfanleg uppsetning dreifðs kerfis

Í þessari færslu viljum við deila áhugaverðri leið til að takast á við uppsetningu á dreifðu kerfi.
Stillingin er sýnd beint á Scala tungumáli á tegundaröruggan hátt. Dæmi um útfærslu er lýst í smáatriðum. Fjallað er um ýmsa þætti tillögunnar, þar á meðal áhrif á heildarþróunarferlið.

Samhæfanleg uppsetning dreifðs kerfis

(á rússnesku)

Hvernig gengur lífið dag frá degi? Er það í jafnvægi og allt eins og það á að vera? Er jafnvægi hvort sem litið er á veraldlega stöðu eða andlega? Lífið er eins og það er. Það er ekki alltaf sólskyn. Það koma reglulega lægðir með rok og rigningu. Við vitum að í heildar samhenginu er lægð hluti af vistkerfi að leita að jafnvægi. Stundum erum við stödd í miðju lægðarinnar. Þar er logn og gott veður, sama hvað gengur á þar sem stormurinn er mestur. Sama lögmál gildir varðandi þitt eigið líf. Ef þú ert í þinn miðju, þínum sannleik þá heldur þú alltaf jafnvægi átakalaust. Sama hvað gustar mikið frá þér þegar þú lætur til þín taka. Huldufólk hefur gefið okkur hugleiðslu sem hjálpar okkur að finna þessa miðju, finna kjarna okkar og sannleikann sem í honum býr. Þegar þú veist hver þú ert og hvers vegna þú ert hér, mun líf þitt vera í flæðandi jafnvægi. Hugleiðslan virkjar þekkinguna sem er í vitund jarðar og færir hana með lífsorkunni inn í líkama okkar. Þar skoðar hún hugsana og hegðunar munstrið og athugar hvort það myndar átakalausu flæðandi jafnvægi. Hinn möguleikinn er falskt jafnvægi sem hafa þarf fyrir að viðhalda með tilheyrandi striti, áhyggjum og ótta. Síðan leiðbeinir þessi þekking okkur að því jafnvægi sem er okkur eðlilegt. Við blómstrum átakalaust, líkt og planta sem vex átakalaut frá fræi í fullþroska plöntu sem ber ávöxt.

Að byggja upp öflug dreifð kerfi krefst notkunar á réttri og samfelldri uppsetningu á öllum hnútum. Dæmigerð lausn er að nota textauppsetningarlýsingu (terraform, ansible eða eitthvað álíka) og sjálfkrafa útbúnar stillingarskrár (oft - tileinkaðar fyrir hvern hnút/hlutverk). Við myndum líka vilja nota sömu samskiptareglur af sömu útgáfum á hverjum samskiptahnút (annars myndum við upplifa ósamrýmanleikavandamál). Í JVM heimi þýðir þetta að að minnsta kosti skilaboðasafnið ætti að vera af sömu útgáfu á öllum samskiptahnútum.

Hvað með að prófa kerfið? Auðvitað ættum við að hafa einingapróf fyrir alla íhluti áður en við komum í samþættingarpróf. Til að geta framreiknað prófunarniðurstöður á keyrslutíma ættum við að ganga úr skugga um að útgáfum allra bókasöfna sé haldið eins í bæði keyrslutíma og prófunarumhverfi.

Þegar samþættingarpróf eru keyrð er oft miklu auðveldara að hafa sömu bekkjarleiðina á öllum hnútum. Við þurfum bara að ganga úr skugga um að sama classpath sé notuð við uppsetningu. (Það er hægt að nota mismunandi bekkjarslóðir á mismunandi hnútum, en það er erfiðara að tákna þessa uppsetningu og dreifa henni rétt.) Svo til að hafa hlutina einfalda munum við aðeins íhuga eins flokksslóðir á öllum hnútum.

Stillingar hafa tilhneigingu til að þróast með hugbúnaðinum. Við notum venjulega útgáfur til að bera kennsl á ýmsar
stig hugbúnaðarþróunar. Það virðist sanngjarnt að ná yfir stillingar undir útgáfustjórnun og bera kennsl á mismunandi stillingar með sumum merkjum. Ef það er aðeins ein uppsetning í framleiðslu, gætum við notað eina útgáfu sem auðkenni. Stundum gætum við haft mörg framleiðsluumhverfi. Og fyrir hvert umhverfi gætum við þurft sérstaka grein af uppsetningu. Þannig að stillingar gætu verið merktar með grein og útgáfu til að bera kennsl á mismunandi stillingar. Hvert útibúsmerki og útgáfa samsvarar einni samsetningu dreifðra hnúta, gátta, ytri auðlinda, útgáfur af flokksstígabókasafni á hverjum hnút. Hér munum við aðeins fjalla um eina greinina og auðkenna stillingar með þriggja þátta aukastafaútgáfu (1.2.3), á sama hátt og aðrir gripir.

Í nútímaumhverfi er stillingarskrám ekki lengur breytt handvirkt. Venjulega myndum við
stillingarskrár á dreifingartíma og aldrei snerta þá á eftir. Svo mætti ​​spyrja hvers vegna notum við enn textasnið fyrir stillingarskrár? Raunhæfur valkostur er að setja uppsetninguna inni í samantektareiningu og njóta góðs af staðfestingu á samsetningartíma.

Í þessari færslu munum við skoða hugmyndina um að halda uppsetningunni í samsetta gripnum.

Samanleg stilling

Í þessum hluta munum við ræða dæmi um kyrrstöðustillingar. Verið er að stilla og innleiða tvær einfaldar þjónustur - bergmálsþjónusta og viðskiptavinur bergmálsþjónustunnar. Þá eru tvö mismunandi dreifð kerfi með báðar þjónusturnar sýndar. Einn er fyrir uppsetningu á einum hnút og annar fyrir uppsetningu tveggja hnúta.

Dæmigerð dreifð kerfi samanstendur af nokkrum hnútum. Hægt væri að bera kennsl á hnúðana með því að nota einhverja gerð:

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

eða bara

case class NodeId(hostName: String)

eða jafnvel

object Singleton
type NodeId = Singleton.type

Þessir hnútar gegna ýmsum hlutverkum, reka einhverja þjónustu og ættu að geta átt samskipti við hina hnúta með TCP/HTTP tengingum.

Fyrir TCP tengingu þarf að minnsta kosti gáttarnúmer. Við viljum líka ganga úr skugga um að viðskiptavinur og netþjónn séu að tala um sömu samskiptareglur. Til þess að móta tengingu milli hnúta skulum við lýsa yfir eftirfarandi flokki:

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

þar sem Port er bara an Int innan leyfilegra marka:

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

Hreinsaðar tegundir

Sjá hreinsaður bókasafn. Í stuttu máli gerir það kleift að bæta við tímatakmörkunum við aðrar gerðir. Í þessu tilfelli Int er aðeins heimilt að hafa 16 bita gildi sem geta táknað gáttarnúmer. Það er engin krafa um að nota þetta bókasafn fyrir þessa stillingaraðferð. Það virðist bara passa mjög vel.

Fyrir HTTP (REST) ​​gætum við líka þurft slóð þjónustunnar:

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

Phantom gerð

Til þess að bera kennsl á samskiptareglur meðan á söfnun stendur erum við að nota Scala eiginleikann til að lýsa yfir tegundarrökum Protocol sem er ekki notað í bekknum. Það er svokallað draugategund. Á keyrslutíma þurfum við sjaldan tilvik af samskiptaauðkenni, þess vegna geymum við það ekki. Við söfnun veitir þessi draugategund aukið öryggi. Við getum ekki framhjá höfn með rangri samskiptareglu.

Ein mest notaða samskiptareglan er REST API með Json serialization:

sealed trait JsonHttpRestProtocol[RequestMessage, ResponseMessage]

þar sem RequestMessage er grunntegund skilaboða sem viðskiptavinur getur sent til netþjóns og ResponseMessage er svarskilaboð frá þjóni. Auðvitað getum við búið til aðrar samskiptareglur sem tilgreina samskiptareglurnar með þeirri nákvæmni sem óskað er eftir.

Í tilgangi þessarar færslu munum við nota einfaldari útgáfu af samskiptareglunum:

sealed trait SimpleHttpGetRest[RequestMessage, ResponseMessage]

Í þessari samskiptareglu er beiðni skilaboðum bætt við vefslóð og svarskilaboðum er skilað sem látlausum streng.

Þjónustustillingu gæti verið lýst með nafni þjónustunnar, safni gátta og sumum ósjálfstæðum. Það eru nokkrar mögulegar leiðir til að tákna alla þessa þætti í Scala (til dæmis, HList, algebruískar gagnategundir). Í tilgangi þessarar færslu munum við nota kökumynstur og tákna sameinanleg stykki (einingar) sem eiginleika. (Kökumynstur er ekki skilyrði fyrir þessa samstilltu stillingaraðferð. Það er bara ein möguleg útfærsla á hugmyndinni.)

Hægt væri að tákna ósjálfstæði með því að nota kökumynstrið sem endapunkta annarra hnúta:

  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 þjónusta þarf aðeins port stillt. Og við lýsum því yfir að þessi höfn styður echo protocol. Athugaðu að við þurfum ekki að tilgreina tiltekna höfn í augnablikinu, vegna þess að eiginleiki leyfir óhlutbundna aðferðayfirlýsingar. Ef við notum abstrakt aðferðir mun þýðandinn krefjast útfærslu í stillingartilviki. Hér höfum við útvegað framkvæmdina (8081) og það verður notað sem sjálfgefið gildi ef við sleppum því í steypuuppsetningu.

Við getum lýst yfir ósjálfstæði í uppsetningu echo þjónustu biðlarans:

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

Ósjálfstæði hefur sömu tegund og echoService. Sérstaklega krefst það sömu siðareglur. Þess vegna getum við verið viss um að ef við tengjum þessa tvo ósjálfstæði þá virka þeir rétt.

Innleiðing þjónustu

Þjónusta þarf aðgerð til að byrja og loka á þokkafullan hátt. (Hæfni til að loka þjónustu er mikilvæg fyrir prófun.) Aftur eru nokkrir möguleikar til að tilgreina slíka aðgerð fyrir tiltekna stillingu (til dæmis gætum við notað tegundaflokka). Fyrir þessa færslu munum við nota Cake Pattern aftur. Við getum táknað þjónustu sem notar cats.Resource sem veitir nú þegar sviga og auðlindaútgáfu. Til þess að fá tilföng ættum við að gefa upp stillingar og eitthvað afturkreistingarsamhengi. Þannig að upphafsaðgerðin gæti litið svona út:

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

þar sem

  • Config — gerð stillingar sem þessi þjónusturæsi krefst
  • AddressResolver — keyrsluhlutur sem hefur getu til að fá raunveruleg heimilisföng annarra hnúta (haltu áfram að lesa til að fá nánari upplýsingar).

hinar tegundirnar koma frá cats:

  • F[_] — tegund áhrifa (Í einfaldasta tilfellinu F[A] gæti verið bara () => A. Í þessari færslu munum við nota cats.IO.)
  • Reader[A,B] — er meira og minna samheiti yfir fall A => B
  • cats.Resource - hefur leiðir til að eignast og gefa út
  • Timer — gerir kleift að sofa/mæla tíma
  • ContextShift - hliðstæða af ExecutionContext
  • Applicative - umbúðir aðgerða í gildi (næstum mónad) (við gætum að lokum skipt henni út fyrir eitthvað annað)

Með því að nota þetta viðmót getum við innleitt nokkrar þjónustur. Til dæmis, þjónusta sem gerir ekkert:

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

(Sjá Kóðinn fyrir aðra þjónustuútfærslu - bergmálsþjónusta,
echo viðskiptavinur og líftíma stýringar.)

Hnútur er einn hlutur sem keyrir nokkrar þjónustur (að hefja keðju auðlinda er virkjuð af Cake Pattern):

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

Athugaðu að í hnútnum tilgreinum við nákvæma gerð stillingar sem þarf fyrir þennan hnút. Þýðandi leyfir okkur ekki að smíða hlutinn (kaka) með ófullnægjandi gerð, vegna þess að hver þjónustueiginleiki lýsir yfir takmörkun á Config gerð. Einnig munum við ekki geta ræst hnút án þess að veita fullkomna uppsetningu.

Upplausn hnút heimilisfangs

Til að koma á tengingu þurfum við raunverulegt hýsingarfang fyrir hvern hnút. Það gæti verið vitað seinna en aðrir hlutar stillingarinnar. Þess vegna þurfum við leið til að leggja fram kortlagningu milli auðkennis hnúts og raunverulegs heimilisfangs þess. Þessi kortlagning er fall:

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

Það eru nokkrar mögulegar leiðir til að útfæra slíka aðgerð.

  1. Ef við þekkjum raunveruleg heimilisföng fyrir uppsetningu, meðan á hnúthýsingar stendur, þá getum við búið til Scala kóða með raunverulegum vistföngum og keyrt bygginguna á eftir (sem framkvæmir samantektartíma og keyrir síðan samþættingarprófunarpakka). Í þessu tilviki er kortlagningaraðgerðin okkar þekkt statískt og hægt er að einfalda hana í eitthvað eins og a Map[NodeId, NodeAddress].
  2. Stundum fáum við raunveruleg heimilisföng aðeins seinna þegar hnúturinn er í raun ræstur, eða við höfum ekki heimilisföng hnúta sem hafa ekki verið ræst ennþá. Í þessu tilfelli gætum við verið með uppgötvunarþjónustu sem er ræst á undan öllum öðrum hnútum og hver hnút gæti auglýst heimilisfang sitt í þeirri þjónustu og gerst áskrifandi að ósjálfstæði.
  3. Ef við getum breytt /etc/hosts, við getum notað fyrirfram skilgreind hýsilheiti (eins og my-project-main-node og echo-backend) og tengdu bara þetta nafn við IP-tölu við uppsetningu.

Í þessari færslu munum við ekki fjalla nánar um þessi mál. Reyndar í leikfangadæminu okkar munu allir hnútar hafa sömu IP tölu - 127.0.0.1.

Í þessari færslu munum við íhuga tvö dreifð kerfisskipulag:

  1. Skipulag staks hnúts, þar sem allar þjónustur eru settar á einn hnút.
  2. Tvö hnútaskipulag, þar sem þjónusta og viðskiptavinur eru á mismunandi hnútum.

Stillingin fyrir a einn hnút skipulag er sem hér segir:

Stilling á einum hnút

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

Hér búum við til eina stillingu sem framlengir bæði netþjóna og biðlara stillingar. Einnig stillum við líftímastýringu sem mun venjulega loka biðlara og netþjóni eftir það lifetime millibili líður.

Hægt er að nota sama sett af þjónustuútfærslum og stillingum til að búa til skipulag kerfis með tveimur aðskildum hnútum. Við þurfum bara að skapa tvær aðskildar hnútstillingar með viðeigandi þjónustu:

Uppsetning tveggja hnúta

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

Sjáðu hvernig við tilgreinum ósjálfstæði. Við nefnum þjónustu hins hnútsins sem er háð núverandi hnút. Gerð ósjálfstæðis er merkt vegna þess að hún inniheldur phantom type sem lýsir samskiptareglum. Og á keyrslutíma munum við hafa rétt hnútauðkenni. Þetta er einn af mikilvægum þáttum fyrirhugaðrar uppsetningaraðferðar. Það veitir okkur möguleika á að stilla höfn aðeins einu sinni og ganga úr skugga um að við séum að vísa til réttrar höfn.

Útfærsla á tveimur hnútum

Fyrir þessa uppsetningu notum við nákvæmlega sömu þjónustuútfærslur. Alls engar breytingar. Hins vegar búum við til tvær mismunandi hnútútfærslur sem innihalda mismunandi þjónustuflokk:

  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
  }

Fyrsti hnúturinn útfærir netþjón og hann þarf aðeins stillingar á miðlarahlið. Annar hnúturinn útfærir biðlara og þarf annan hluta af stillingum. Báðir hnútar krefjast einhverrar lífstímaforskrift. Að því er varðar þessa póstþjónustu mun hnútur hafa óendanlegan líftíma sem hægt er að slíta með því að nota SIGTERM, á meðan echo viðskiptavinur lýkur eftir stillta endanlega tímalengd. Sjáðu byrjendaumsókn nánari upplýsingar.

Heildarþróunarferli

Við skulum sjá hvernig þessi nálgun breytir því hvernig við vinnum með stillingar.

Stillingin sem kóða verður tekin saman og framleiðir grip. Það virðist sanngjarnt að aðskilja stillingargripi frá öðrum kóðagripum. Oft getum við haft margar stillingar á sama kóðagrunni. Og auðvitað getum við haft margar útgáfur af ýmsum uppsetningargreinum. Í stillingum getum við valið sérstakar útgáfur af bókasöfnum og þetta mun haldast stöðugt í hvert skipti sem við setjum þessa uppsetningu upp.

Stillingarbreyting verður kóðabreyting. Þannig að það ætti að falla undir sama gæðatryggingarferli:

Miði -> PR -> endurskoðun -> sameina -> samfelld samþætting -> samfelld dreifing

Það eru eftirfarandi afleiðingar af nálguninni:

  1. Uppsetningin er samfelld fyrir tilvik tiltekins kerfis. Það virðist sem engin leið sé til að hafa ranga tengingu milli hnúta.
  2. Það er ekki auðvelt að breyta stillingum bara í einum hnút. Það virðist óeðlilegt að skrá sig inn og breyta einhverjum textaskrám. Þannig að stillingarrek verður síður mögulegt.
  3. Ekki er auðvelt að gera litlar stillingarbreytingar.
  4. Flestar stillingarbreytingar munu fylgja sama þróunarferli og það mun standast nokkra endurskoðun.

Þurfum við sérstaka geymslu fyrir framleiðslustillingar? Framleiðsluuppsetningin gæti innihaldið viðkvæmar upplýsingar sem við viljum halda utan seilingar fyrir marga. Svo það gæti verið þess virði að halda sérstakri geymslu með takmörkuðum aðgangi sem mun innihalda framleiðslustillingarnar. Við gætum skipt uppsetningunni í tvo hluta - einn sem inniheldur opnustu breytur framleiðslu og einn sem inniheldur leynihluta uppsetningar. Þetta myndi gera flestum forriturum kleift að fá aðgang að langflestum breytum en takmarka aðgang að mjög viðkvæmum hlutum. Það er auðvelt að ná þessu með því að nota millieiginleika með sjálfgefnum breytugildum.

Tilbrigði

Við skulum sjá kosti og galla fyrirhugaðrar nálgunar miðað við aðrar stillingarstjórnunaraðferðir.

Fyrst af öllu munum við skrá nokkra valkosti við mismunandi þætti fyrirhugaðrar leiðar til að takast á við uppsetningu:

  1. Textaskrá á markvélinni.
  2. Miðstýrð lykilgildi geymsla (eins og etcd/zookeeper).
  3. Undirferlisíhlutir sem hægt væri að endurstilla/endurræsa án þess að endurræsa ferlið.
  4. Stillingar utan grips og útgáfustýringar.

Textaskrá gefur nokkurn sveigjanleika hvað varðar sérstakar lagfæringar. Kerfisstjóri getur skráð sig inn á markhnútinn, gert breytingar og einfaldlega endurræst þjónustuna. Þetta gæti ekki verið eins gott fyrir stærri kerfi. Engin ummerki eru skilin eftir breytinguna. Breytingin er ekki endurskoðuð af öðrum augum. Það gæti verið erfitt að komast að því hvað hefur valdið breytingunni. Það hefur ekki verið prófað. Frá sjónarhóli dreifðs kerfis getur stjórnandi einfaldlega gleymt að uppfæra stillingarnar í einum af hinum hnútunum.

(Btw, ef að lokum verður þörf á að byrja að nota textastillingarskrár, verðum við aðeins að bæta við parser + validator sem gæti framleitt það sama Config tegund og það væri nóg til að byrja að nota textastillingar. Þetta sýnir líka að flókið samsetningartímastillingar er aðeins minna en flókið textatengdrar stillingar, vegna þess að í textatengdri útgáfu þurfum við einhvern viðbótarkóða.)

Miðstýrð lykilgilda geymsla er góður búnaður til að dreifa meta breytum forrita. Hér þurfum við að hugsa um hvað við teljum vera stillingargildi og hvað eru bara gögn. Gefin fall C => A => B við köllum venjulega sjaldan breytast gildi C "stillingar", en oft breytt gögn A - sláðu bara inn gögn. Stillingar ættu að koma til aðgerðarinnar fyrr en gögnin A. Miðað við þessa hugmynd getum við sagt að það sé áætluð tíðni breytinga sem hægt væri að nota til að greina uppsetningargögn frá aðeins gögnum. Einnig koma gögn venjulega frá einum uppruna (notanda) og stillingar koma frá öðrum uppruna (admin). Að takast á við færibreytur sem hægt er að breyta eftir frumstillingarferlið leiðir til aukinnar flóknar umsóknar. Fyrir slíkar færibreytur verðum við að sjá um afhendingarkerfi þeirra, þáttun og staðfestingu, meðhöndla röng gildi. Þess vegna, til þess að draga úr flóknu forriti, ættum við að fækka þeim breytum sem geta breyst á keyrslutíma (eða jafnvel útrýma þeim alveg).

Frá sjónarhóli þessarar færslu ættum við að gera greinarmun á kyrrstæðum og kvikum breytum. Ef þjónusturökfræði krefst sjaldgæfra breytinga á sumum breytum á keyrslutíma, þá getum við kallað þær kvikar breytur. Annars eru þau kyrrstæð og hægt væri að stilla þær með fyrirhugaðri nálgun. Fyrir kraftmikla endurstillingu gæti verið þörf á öðrum aðferðum. Til dæmis gætu hlutar kerfisins verið endurræstir með nýju stillingarbreytunum á svipaðan hátt og að endurræsa aðskilin ferli dreifðs kerfis.
(Mín auðmjúk skoðun er að forðast endurstillingu keyrslutíma vegna þess að það eykur flókið kerfi.
Það gæti verið einfaldara að treysta bara á stýrikerfisstuðning við að endurræsa ferla. Þó gæti það ekki alltaf verið hægt.)

Einn mikilvægur þáttur í notkun kyrrstæðrar stillingar sem stundum fær fólk til að íhuga kraftmikla stillingu (án annarra ástæðna) er niður í miðbænum meðan á uppfærslu stendur. Reyndar, ef við þurfum að gera breytingar á kyrrstöðuuppsetningu, verðum við að endurræsa kerfið svo að ný gildi verði virk. Kröfurnar um niður í miðbæ eru mismunandi fyrir mismunandi kerfi, þannig að það gæti ekki verið svo mikilvægt. Ef það er mikilvægt, þá verðum við að skipuleggja fyrirfram fyrir endurræsingu kerfisins. Til dæmis gætum við útfært AWS ELB tengitæmsla. Í þessari atburðarás hvenær sem við þurfum að endurræsa kerfið, byrjum við nýtt tilvik af kerfinu samhliða, skiptum svo ELB yfir í það, en leyfum gamla kerfinu að ljúka við að þjónusta núverandi tengingar.

Hvað með að halda stillingum inni í útgefnum gripi eða utan? Að halda stillingum inni í gripi þýðir í flestum tilfellum að þessi stilling hefur staðist sama gæðatryggingarferli og aðrir gripir. Svo maður gæti verið viss um að uppsetningin sé af góðum gæðum og áreiðanleg. Þvert á móti þýðir uppsetning í sérstakri skrá að það eru engin ummerki um hver og hvers vegna gerði breytingar á þeirri skrá. Er þetta mikilvægt? Við teljum að fyrir flest framleiðslukerfi sé betra að hafa stöðuga og hágæða uppsetningu.

Útgáfa gripsins gerir kleift að komast að því hvenær hann var búinn til, hvaða gildi hann inniheldur, hvaða eiginleikar eru virkjaðir/óvirkir, hver var ábyrgur fyrir hverri breytingu á uppsetningunni. Það gæti þurft áreynslu til að halda stillingum inni í gripi og það er hönnunarval sem þarf að gera.

Kostir Gallar

Hér viljum við draga fram nokkra kosti og ræða nokkra ókosti fyrirhugaðrar aðferðar.

Kostir

Eiginleikar samstillanlegrar uppsetningar á fullkomnu dreifðu kerfi:

  1. Statísk athugun á stillingum. Þetta gefur mikið öryggi, að uppsetningin sé rétt miðað við tegundarþvinganir.
  2. Ríkt tungumál stillingar. Venjulega eru aðrar stillingaraðferðir takmarkaðar við í mesta lagi breytilega skiptingu.
    Með því að nota Scala er hægt að nota fjölbreytt úrval tungumálaeiginleika til að gera uppsetningu betri. Til dæmis getum við notað eiginleika til að gefa upp sjálfgefin gildi, hluti til að stilla mismunandi umfang, við getum vísað til vals skilgreint aðeins einu sinni í ytra umfangi (DRY). Það er hægt að nota bókstafsraðir, eða tilvik af ákveðnum flokkum (Seq, Map, O.fl.).
  3. DSL. Scala hefur ágætis stuðning fyrir DSL rithöfunda. Hægt er að nota þessa eiginleika til að koma á stillingartungumáli sem er þægilegra og notendavænna, þannig að endanleg uppsetning sé að minnsta kosti læsileg fyrir notendur léns.
  4. Heiðarleiki og samræmi þvert á hnúta. Einn af kostunum við að hafa uppsetningu fyrir allt dreifða kerfið á einum stað er að öll gildi eru skilgreind nákvæmlega einu sinni og síðan endurnotuð á öllum stöðum þar sem við þurfum á þeim að halda. Sláðu einnig inn örugga höfn yfirlýsingar tryggja að í öllum mögulegum réttum stillingum munu hnútar kerfisins tala sama tungumálið. Það eru skýr ósjálfstæði milli hnúta sem gerir það erfitt að gleyma að veita einhverja þjónustu.
  5. Mikil gæði breytinga. Heildaraðferðin við að senda stillingarbreytingar í gegnum venjulegt PR-ferli setur háa gæðakröfur einnig í uppsetningu.
  6. Samtímis stillingarbreytingar. Alltaf þegar við gerum einhverjar breytingar á stillingunum tryggir sjálfvirk uppsetning að allir hnútar séu uppfærðir.
  7. Einföldun umsókna. Forritið þarf ekki að flokka og staðfesta stillingar og meðhöndla röng stillingargildi. Þetta einfaldar heildarumsóknina. (Einhver flókin aukning er í uppsetningunni sjálfri, en það er meðvitað skipting í átt að öryggi.) Það er frekar einfalt að fara aftur í venjulega uppsetningu - bættu bara við þeim hlutum sem vantar. Það er auðveldara að hefjast handa með samstilltum stillingum og fresta innleiðingu aukahluta til síðari tíma.
  8. Útgáfa uppsetning. Vegna þess að stillingarbreytingar fylgja sama þróunarferli, fáum við grip með einstaka útgáfu. Það gerir okkur kleift að skipta um stillingar til baka ef þörf krefur. Við getum jafnvel sett upp stillingu sem var notuð fyrir ári síðan og hún mun virka nákvæmlega á sama hátt. Stöðug uppsetning bætir fyrirsjáanleika og áreiðanleika dreifða kerfisins. Stillingin er fast á þýðingu tíma og ekki er auðvelt að fikta í framleiðslukerfi.
  9. Modularity. Fyrirhuguð rammi er mát og hægt er að sameina einingar á ýmsan hátt til að
    styðja mismunandi stillingar (uppsetningar/útlit). Sérstaklega er hægt að hafa smáskala skipulag fyrir stakan hnút og fjölhnútastillingu í stórum stíl. Það er sanngjarnt að hafa mörg framleiðsluskipulag.
  10. Prófanir. Í prófunartilgangi gæti maður innleitt spottþjónustu og notað hana sem ósjálfstæði á öruggan hátt. Hægt var að viðhalda nokkrum mismunandi prófunaruppsetningum með ýmsum hlutum sem skipt er út fyrir spotta samtímis.
  11. Samþættingarpróf. Stundum í dreifðum kerfum er erfitt að keyra samþættingarpróf. Með því að nota lýst nálgun til að slá inn örugga uppsetningu á öllu dreifðu kerfi, getum við keyrt alla dreifða hluta á einum netþjóni á stjórnanlegan hátt. Það er auðvelt að líkja eftir aðstæðum
    þegar ein af þjónustunum verður ótæk.

Ókostir

Samsetta stillingaraðferðin er frábrugðin „venjulegri“ stillingu og gæti ekki hentað öllum þörfum. Hér eru nokkrir af ókostum samsettrar stillingar:

  1. Statísk stilling. Það gæti ekki hentað öllum forritum. Í sumum tilfellum er þörf á að laga uppsetninguna í framleiðslu fljótt framhjá öllum öryggisráðstöfunum. Þessi nálgun gerir það erfiðara. Samantekt og endurdreifing er nauðsynleg eftir að breytingar hafa verið gerðar á uppsetningu. Þetta er bæði eiginleiki og byrði.
  2. Stillingar kynslóð. Þegar stillingar eru búnar til af einhverju sjálfvirkniverkfæri krefst þessi nálgun síðari samantektar (sem gæti aftur mistekist). Það gæti þurft frekari áreynslu til að samþætta þetta viðbótarskref inn í byggingarkerfið.
  3. Hljóðfæri. Það eru fullt af verkfærum í notkun í dag sem treysta á textabyggðar stillingar. Sumir þeirra
    mun ekki eiga við þegar stillingar eru settar saman.
  4. Hugarfarsbreytingu er þörf. Hönnuðir og DevOps þekkja textastillingarskrár. Hugmyndin um að setja saman stillingar gæti virst þeim undarleg.
  5. Áður en samstillanleg stilling er kynnt þarf hágæða hugbúnaðarþróunarferli.

Það eru nokkrar takmarkanir á útfærða dæminu:

  1. Ef við bjóðum upp á auka stillingar sem ekki er krafist af hnútútfærslunni, mun þýðandinn ekki hjálpa okkur að greina fjarverandi útfærslu. Hægt væri að bregðast við þessu með því að nota HList eða ADTs (case classes) fyrir hnútstillingar í stað eiginleika og kökumynsturs.
  2. Við verðum að útvega smá boilerplate í stillingarskrá: (package, import, object yfirlýsingar;
    override def's fyrir færibreytur sem hafa sjálfgefin gildi). Þetta gæti verið leyst að hluta með því að nota DSL.
  3. Í þessari færslu er ekki fjallað um kraftmikla endurstillingu klasa af svipuðum hnútum.

Niðurstaða

Í þessari færslu höfum við rætt hugmyndina um að tákna stillingar beint í frumkóðann á öruggan hátt. Hægt væri að nota nálgunina í mörgum forritum sem staðgengill fyrir xml- og aðrar textatengdar stillingar. Þrátt fyrir að dæmið okkar hafi verið útfært í Scala, gæti það líka verið þýtt á önnur samhæfanleg tungumál (eins og Kotlin, C#, Swift, osfrv.). Maður gæti prófað þessa nálgun í nýju verkefni og, ef það passar ekki vel, skipt yfir á gamla mátann.

Auðvitað krefst samstillanleg uppsetning hágæða þróunarferlis. Í staðinn lofar það að veita jafn hágæða sterka uppsetningu.

Hægt er að útvíkka þessa nálgun á ýmsa vegu:

  1. Hægt væri að nota fjölvi til að framkvæma löggildingu stillinga og mistakast á þýðingu tíma ef einhverjar viðskiptarökfræðilegar takmarkanir bila.
  2. Hægt væri að útfæra DSL til að tákna uppsetningu á notendavænan hátt fyrir lén.
  3. Kvik auðlindastjórnun með sjálfvirkum stillingum. Til dæmis, þegar við stillum fjölda klasahnúta gætum við viljað (1) að hnútarnir fái örlítið breytta uppsetningu; (2) klasastjóri til að fá nýjar upplýsingar um hnúta.

Takk

Mig langar að þakka Andrey Saksonov, Pavel Popov, Anton Nehaev fyrir að gefa hvetjandi endurgjöf um drög þessarar færslu sem hjálpuðu mér að gera hana skýrari.

Heimild: www.habr.com