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Ä.
(
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Ä
Å 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
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 EndPoint
citu 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 veidsAddressResolver
ā 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 izmantosimcats.IO
.)Reader[A,B]
- vairÄk vai mazÄk sinonÄ«ms funkcijaiA => B
cats.Resource
- resurss, ko var iegūt un atbrīvotTimer
ā taimeris (ļauj uz brÄ«di aizmigt un izmÄrÄ«t laika intervÄlus)ContextShift
- analogsExecutionContext
Applicative
ā efektu tipa klase, kas ļauj apvienot atseviŔķus efektus (gandrÄ«z monÄde). SarežģītÄkos lietojumos Ŕķiet labÄk izmantotMonad
/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.
Šø
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:
- 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ÄjumuMap[NodeId, NodeAddress]
. - 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. - 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:
- Visu pakalpojumu ievietoÅ”ana vienÄ mezglÄ.
- Un atbalss pakalpojuma un atbalss klienta mitinÄÅ”ana dažÄdos mezglos.
KonfigurÄcija priekÅ”
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
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.
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:
-
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.
-
Ir problemÄtiski mainÄ«t konfigurÄciju tikai vienÄ no mezgliem. TÄpÄc ākonfigurÄcijas novirzeā ir maz ticama.
-
Kļūst grÅ«tÄk veikt nelielas izmaiÅas konfigurÄcijÄ.
-
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:
- Teksta fails mÄrÄ·a maŔīnÄ.
- CentralizÄta atslÄgu vÄrtÄ«bu krÄtuve (
etcd
/zookeeper
). - Procesa komponenti, kurus var pÄrkonfigurÄt/restartÄt bez procesa restartÄÅ”anas.
- 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
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:
- StatiskÄ konfigurÄcijas pÄrbaude. Ä»auj jums par to pÄrliecinÄties
konfigurÄcija ir pareiza. - 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). - 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Ä.
- 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.
- 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.
- VienlaicÄ«ga konfigurÄcijas atjauninÄÅ”ana. AutomÄtiska sistÄmas izvietoÅ”ana pÄc konfigurÄcijas izmaiÅÄm nodroÅ”ina visu mezglu atjauninÄÅ”anu.
- 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.
- 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.
- 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.
- TestÄÅ”ana. Aizvietojot atseviŔķus pakalpojumus ar imitÄcijas objektiem, jÅ«s varat iegÅ«t vairÄkas testÄÅ”anai Ärtas sistÄmas versijas.
- 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:
- 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.
- 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.
- 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Ä.
- 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.
- 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:
- 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. - 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. - Å Ä«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:
- Varat izmantot makro, lai veiktu kompilÄÅ”anas laika pÄrbaudes.
- Varat ieviest DSL, lai parÄdÄ«tu konfigurÄciju galalietotÄjiem pieejamÄ veidÄ.
- 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