Å ajÄ rakstÄ mÄs vÄlamies dalÄ«ties ar interesantu veidu, kÄ rÄ«koties ar izplatÄ«tÄs sistÄmas konfigurÄÅ”anu.
KonfigurÄcija ir attÄlota tieÅ”i Scala valodÄ droÅ”Ä veidÄ. ÄŖstenoÅ”anas piemÄrs ir detalizÄti aprakstÄ«ts. Tiek apspriesti dažÄdi priekÅ”likuma aspekti, tostarp ietekme uz kopÄjo izstrÄdes procesu.
Ievads
Lai izveidotu stabilas sadalÄ«tas sistÄmas, visos mezglos ir jÄizmanto pareiza un saskaÅota konfigurÄcija. Tipisks risinÄjums ir izmantot tekstuÄlu izvietoÅ”anas aprakstu (terraformu, ansible vai kaut ko lÄ«dzÄ«gu) un automÄtiski Ä£enerÄtus konfigurÄcijas failus (bieži ā katram mezglam/lomai). MÄs arÄ« vÄlamies izmantot tos paÅ”us vienas un tÄs paÅ”as versijas protokolus katrÄ saziÅas mezglÄ (pretÄjÄ gadÄ«jumÄ radÄ«sies nesaderÄ«bas problÄmas). JVM pasaulÄ tas nozÄ«mÄ, ka vismaz ziÅojumapmaiÅas bibliotÄkai ir jÄbÅ«t vienÄdai versijai visos saziÅas mezglos.
KÄ ar sistÄmas testÄÅ”anu? Protams, pirms integrÄcijas testiem mums vajadzÄtu veikt visu komponentu vienÄ«bu testus. Lai varÄtu ekstrapolÄt testa rezultÄtus izpildlaikÄ, mums ir jÄpÄrliecinÄs, ka visu bibliotÄku versijas tiek saglabÄtas identiskas gan izpildlaika, gan testÄÅ”anas vidÄs.
Veicot integrÄcijas testus, bieži vien ir daudz vieglÄk, ja visos mezglos ir viens klases ceļŔ. Mums tikai jÄpÄrliecinÄs, ka izvietoÅ”anÄ tiek izmantots tas pats klases ceļŔ. (Ir iespÄjams izmantot dažÄdus klases ceļus dažÄdos mezglos, taÄu ir grÅ«tÄk attÄlot Å”o konfigurÄciju un pareizi to izvietot.) TÄpÄc, lai lietas bÅ«tu vienkÄrÅ”as, mÄs Åemsim vÄrÄ tikai identiskus klases ceļus visos mezglos.
KonfigurÄcijai ir tendence attÄ«stÄ«ties kopÄ ar programmatÅ«ru. MÄs parasti izmantojam versijas, lai identificÄtu dažÄdas
programmatÅ«ras evolÅ«cijas posmi. Å Ä·iet saprÄtÄ«gi iekļaut versiju pÄrvaldÄ«bÄ konfigurÄciju un identificÄt dažÄdas konfigurÄcijas ar dažÄm iezÄ«mÄm. Ja ražoÅ”anÄ ir tikai viena konfigurÄcija, mÄs varam izmantot vienu versiju kÄ identifikatoru. Dažreiz mums var bÅ«t vairÄkas ražoÅ”anas vides. Un katrai videi mums var bÅ«t nepiecieÅ”ama atseviŔķa konfigurÄcijas nozare. TÄdÄjÄdi konfigurÄcijas var apzÄ«mÄt ar filiÄli un versiju, lai unikÄli identificÄtu dažÄdas konfigurÄcijas. Katra filiÄles etiÄ·ete un versija atbilst vienai sadalÄ«to mezglu, portu, ÄrÄjo resursu un klases ceļa bibliotÄkas versiju kombinÄcijai katrÄ mezglÄ. Å eit mÄs aptversim tikai vienu atzaru un identificÄsim konfigurÄcijas pÄc trÄ«skomponentu decimÄldaļas (1.2.3), tÄpat kÄ citi artefakti.
MÅ«sdienu vidÄ konfigurÄcijas faili vairs netiek modificÄti manuÄli. Parasti mÄs Ä£enerÄjam
konfigurÄcijas faili izvietoÅ”anas laikÄ un
Å ajÄ ierakstÄ mÄs apskatÄ«sim ideju par konfigurÄcijas saglabÄÅ”anu apkopotajÄ artefaktÄ.
KompilÄjamÄ konfigurÄcija
Å ajÄ sadaÄ¼Ä mÄs apspriedÄ«sim statiskÄs konfigurÄcijas piemÄru. Tiek konfigurÄti un ieviesti divi vienkÄrÅ”i pakalpojumi - atbalss pakalpojums un atbalss servisa klients. PÄc tam tiek izveidotas divas dažÄdas sadalÄ«tas sistÄmas ar abiem pakalpojumiem. Viens ir paredzÄts viena mezgla konfigurÄcijai, bet otrs - divu mezglu konfigurÄcijai.
Tipiska izplatÄ«ta sistÄma sastÄv no dažiem mezgliem. Mezglus var identificÄt, izmantojot dažus veidus:
sealed trait NodeId
case object Backend extends NodeId
case object Frontend extends NodeId
vai vienkÄrÅ”i
case class NodeId(hostName: String)
vai pat
object Singleton
type NodeId = Singleton.type
Å ie mezgli pilda dažÄdas lomas, vada dažus pakalpojumus un tiem jÄspÄj sazinÄties ar citiem mezgliem, izmantojot TCP/HTTP savienojumus.
TCP savienojumam ir nepiecieÅ”ams vismaz porta numurs. MÄs arÄ« vÄlamies pÄrliecinÄties, ka klients un serveris runÄ vienÄ protokolÄ. Lai modelÄtu savienojumu starp mezgliem, deklarÄsim Å”Ädu klasi:
case class TcpEndPoint[Protocol](node: NodeId, port: Port[Protocol])
kur Port
ir tikai an Int
atļautajÄ diapazonÄ:
type PortNumber = Refined[Int, Closed[_0, W.`65535`.T]]
RafinÄti veidi
redzÄt Int
ir atļauts izmantot tikai 16 bitu vÄrtÄ«bas, kas var attÄlot porta numuru. Å ai konfigurÄcijas pieejai Ŕī bibliotÄka nav jÄizmanto. Å Ä·iet, ka tas tikai ļoti labi iederas.
HTTP (REST) āāmums var bÅ«t nepiecieÅ”ams arÄ« pakalpojuma ceļŔ:
type UrlPathPrefix = Refined[String, MatchesRegex[W.`"[a-zA-Z_0-9/]*"`.T]]
case class PortWithPrefix[Protocol](portNumber: PortNumber, pathPrefix: UrlPathPrefix)
Fantoma tips
Lai kompilÄcijas laikÄ identificÄtu protokolu, mÄs izmantojam tipa argumenta deklarÄÅ”anas lÄ«dzekli Scala Protocol
kas klasÄ netiek izmantots. Tas ir tÄ sauktais fantoma tips. Izpildes laikÄ mums reti ir nepiecieÅ”ams protokola identifikatora gadÄ«jums, tÄpÄc mÄs to nesaglabÄjam. KompilÄcijas laikÄ Å”is fantoma tips sniedz papildu tipa droŔību. MÄs nevaram iziet cauri portam ar nepareizu protokolu.
Viens no visplaÅ”Äk izmantotajiem protokoliem ir REST API ar Json serializÄciju:
sealed trait JsonHttpRestProtocol[RequestMessage, ResponseMessage]
kur RequestMessage
ir pamata ziÅu veids, ko klients var nosÅ«tÄ«t serverim un ResponseMessage
ir atbildes ziÅojums no servera. Protams, mÄs varam izveidot citus protokolu aprakstus, kas norÄda sakaru protokolu ar vÄlamo precizitÄti.
Å Ä«s ziÅas nolÅ«kos mÄs izmantosim vienkÄrÅ”Äku protokola versiju:
sealed trait SimpleHttpGetRest[RequestMessage, ResponseMessage]
Å ajÄ protokolÄ pieprasÄ«juma ziÅojums tiek pievienots url, un atbildes ziÅojums tiek atgriezts kÄ vienkÄrÅ”a virkne.
Pakalpojuma konfigurÄciju var aprakstÄ«t ar pakalpojuma nosaukumu, portu kolekciju un dažÄm atkarÄ«bÄm. Ir daži iespÄjamie veidi, kÄ visus Å”os elementus attÄlot programmÄ Scala (piemÄram, HList
, algebrisko datu tipi). Å Ä«s ziÅas nolÅ«kos mÄs izmantosim kÅ«kas modeli un attÄlosim kombinÄjamus gabalus (moduļus) kÄ iezÄ«mes. (Å ai kompilÄjamai konfigurÄcijas pieejai kÅ«kas raksts nav obligÄta prasÄ«ba. TÄ ir tikai viena no idejas realizÄcijas iespÄjÄm.)
AtkarÄ«bas var attÄlot, izmantojot kÅ«ka modeli kÄ citu mezglu galapunktus:
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 pakalpojumam ir nepiecieÅ”ams tikai konfigurÄts ports. Un mÄs paziÅojam, ka Å”is ports atbalsta atbalss protokolu. Å
emiet vÄrÄ, ka Å”obrÄ«d mums nav jÄnorÄda konkrÄts ports, jo iezÄ«me ļauj deklarÄt abstraktas metodes. Ja mÄs izmantojam abstraktas metodes, kompilatoram bÅ«s nepiecieÅ”ama ievieÅ”ana konfigurÄcijas instancÄ. Å eit mÄs esam nodroÅ”inÄjuÅ”i ievieÅ”anu (8081
), un tÄ tiks izmantota kÄ noklusÄjuma vÄrtÄ«ba, ja mÄs to izlaidÄ«sim konkrÄtÄ konfigurÄcijÄ.
MÄs varam deklarÄt atkarÄ«bu echo pakalpojuma klienta konfigurÄcijÄ:
trait EchoClientConfig[A] {
def testMessage: String = "test"
def pollInterval: FiniteDuration
def echoServiceDependency: HttpSimpleGetEndPoint[_, EchoProtocol[A]]
}
AtkarÄ«bai ir tÄds pats veids kÄ echoService
. Jo Ä«paÅ”i tas prasa to paÅ”u protokolu. TÄdÄjÄdi mÄs varam bÅ«t pÄrliecinÄti, ka, savienojot Ŕīs divas atkarÄ«bas, tÄs darbosies pareizi.
Pakalpojumu ievieŔana
Pakalpojumam ir nepiecieÅ”ama funkcija, lai palaistu un graciozi izslÄgtu. (SpÄja izslÄgt pakalpojumu ir ļoti svarÄ«ga pÄrbaudei.) Atkal ir dažas iespÄjas, kÄ norÄdÄ«t Å”Ädu funkciju konkrÄtai konfigurÄcijai (piemÄram, mÄs varÄtu izmantot tipu klases). Å ajÄ rakstÄ mÄs atkal izmantosim kÅ«kas modeli. MÄs varam pÄrstÄvÄt pakalpojumu, izmantojot cats.Resource
kas jau nodroÅ”ina dublÄÅ”anu un resursu atbrÄ«voÅ”anu. Lai iegÅ«tu resursu, mums ir jÄnodroÅ”ina konfigurÄcija un kÄds izpildlaika konteksts. TÄtad pakalpojuma palaiÅ”anas funkcija varÄtu 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
ā konfigurÄcijas veids, kas nepiecieÅ”ams Å”im pakalpojuma starterimAddressResolver
ā izpildlaika objekts, kam ir iespÄja iegÅ«t citu mezglu reÄlas adreses (sÄ«kÄku informÄciju lasiet tÄlÄk).
citi veidi nÄk no cats
:
F[_]
ā efekta veids (vienkÄrÅ”ÄkajÄ gadÄ«jumÄF[A]
varÄtu bÅ«t tikai() => A
. Å ajÄ amatÄ mÄs izmantosimcats.IO
.)Reader[A,B]
ā ir vairÄk vai mazÄk sinonÄ«ms funkcijaiA => B
cats.Resource
ā ir veidi, kÄ iegÅ«t un atbrÄ«votTimer
ā ļauj gulÄt/mÄrÄ«t laikuContextShift
- analogsExecutionContext
Applicative
ā spÄkÄ esoÅ”o funkciju iesaiÅojums (gandrÄ«z monÄde) (mÄs to varÄtu aizstÄt ar kaut ko citu)
Izmantojot Å”o saskarni, mÄs varam ieviest dažus 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](()))
}
(Skatīt
Mezgls ir viens objekts, kas darbina dažus pakalpojumus (resursu Ä·Ädes sÄkÅ”anu iespÄjo Cake Pattern):
object SingleNodeImpl extends ZeroServiceImpl[IO]
with EchoServiceService
with EchoClientService
with FiniteDurationLifecycleServiceImpl
{
type Config = EchoConfig[String] with EchoClientConfig[String] with FiniteDurationLifecycleConfig
}
Å
emiet vÄrÄ, ka mezglÄ mÄs norÄdÄm precÄ«zu konfigurÄcijas veidu, kas nepiecieÅ”ams Å”im mezglam. Kompilators neļaus mums izveidot objektu (Cake) ar nepietiekamu tipu, jo katra pakalpojuma iezÄ«me deklarÄ ierobežojumu Config
veids. Bez pilnÄ«gas konfigurÄcijas mÄs nevarÄsim palaist mezglu.
Mezglu adreses izŔķirtspÄja
Lai izveidotu savienojumu, katram mezglam ir nepiecieÅ”ama reÄla resursdatora adrese. Tas varÄtu bÅ«t zinÄms vÄlÄk nekÄ citas konfigurÄcijas daļas. TÄdÄjÄdi mums ir nepiecieÅ”ams veids, kÄ nodroÅ”inÄt kartÄÅ”anu starp mezgla ID un tÄ faktisko adresi. Å Ä« kartÄÅ”ana ir funkcija:
case class NodeAddress[NodeId](host: Uri.Host)
trait AddressResolver[F[_]] {
def resolve[NodeId](nodeId: NodeId): F[NodeAddress[NodeId]]
}
Ir daži iespÄjamie veidi, kÄ Ä«stenot Å”Ädu funkciju.
- Ja mÄs zinÄm faktiskÄs adreses pirms izvietoÅ”anas, mezgla saimniekdatora inscenÄÅ”anas laikÄ, mÄs varam Ä£enerÄt Scala kodu ar faktiskajÄm adresÄm un pÄc tam palaist bÅ«vÄjumu (kas veic kompilÄÅ”anas laika pÄrbaudes un pÄc tam palaiž integrÄcijas testu komplektu). Å ajÄ gadÄ«jumÄ mÅ«su kartÄÅ”anas funkcija ir zinÄma statiski, un to var vienkÄrÅ”ot lÄ«dz a
Map[NodeId, NodeAddress]
. - Dažreiz mÄs iegÅ«stam faktiskÄs adreses tikai vÄlÄkÄ brÄ«dÄ«, kad mezgls faktiski tiek palaists, vai arÄ« mums nav vÄl nesÄktu mezglu adreses. Å ajÄ gadÄ«jumÄ mums var bÅ«t atklÄÅ”anas pakalpojums, kas tiek palaists pirms visiem citiem mezgliem, un katrs mezgls var reklamÄt savu adresi Å”ajÄ pakalpojumÄ un abonÄt atkarÄ«bas.
- Ja mÄs varam mainÄ«t
/etc/hosts
, mÄs varam izmantot iepriekÅ” definÄtus resursdatora nosaukumus (piemÄram,my-project-main-node
unecho-backend
) un vienkÄrÅ”i saistiet Å”o nosaukumu ar IP adresi izvietoÅ”anas laikÄ.
Å ajÄ ziÅojumÄ mÄs sÄ«kÄk neaptveram Å”os gadÄ«jumus. Faktiski mÅ«su rotaļlietu piemÄrÄ visiem mezgliem bÅ«s viena un tÄ pati IP adrese - 127.0.0.1
.
Å ajÄ ziÅojumÄ mÄs apskatÄ«sim divus izplatÄ«tÄs sistÄmas izkÄrtojumus:
- Viena mezgla izkÄrtojums, kur visi pakalpojumi ir izvietoti vienÄ mezglÄ.
- Divu mezglu izkÄrtojums, kur pakalpojums un klients atrodas dažÄdos mezglos.
KonfigurÄcija a
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.
}
Å eit mÄs izveidojam vienu konfigurÄciju, kas paplaÅ”ina gan servera, gan klienta konfigurÄciju. MÄs arÄ« konfigurÄjam dzÄ«ves cikla kontrolieri, kas pÄc tam parasti pÄrtrauc klienta un servera darbÄ«bu lifetime
intervÄls iet.
To paÅ”u pakalpojumu ievieÅ”anu un konfigurÄciju kopu var izmantot, lai izveidotu sistÄmas izkÄrtojumu ar diviem atseviŔķiem mezgliem. Mums tikai jÄrada
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"
}
Skatiet, kÄ mÄs norÄdÄm atkarÄ«bu. MÄs pieminam otra mezgla sniegto pakalpojumu kÄ paÅ”reizÄjÄ mezgla atkarÄ«bu. AtkarÄ«bas veids ir pÄrbaudÄ«ts, jo tajÄ ir fantoma tips, kas apraksta protokolu. Un izpildlaikÄ mums bÅ«s pareizais mezgla ID. Å is ir viens no svarÄ«gÄkajiem piedÄvÄtÄs konfigurÄcijas pieejas aspektiem. Tas nodroÅ”ina mums iespÄju iestatÄ«t portu tikai vienu reizi un pÄrliecinÄties, ka mÄs atsaucamies uz pareizo portu.
Divu mezglu ievieŔana
Å ai konfigurÄcijai mÄs izmantojam tieÅ”i tÄdas paÅ”as pakalpojumu ievieÅ”anas. IzmaiÅu vispÄr nav. TomÄr mÄs izveidojam divus dažÄdus mezglu implementÄcijas, kas satur 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 puses konfigurÄcija. Otrais mezgls ievieÅ” klientu, un tam ir nepiecieÅ”ama cita konfigurÄcijas daļa. Abiem mezgliem ir nepiecieÅ”ama noteikta ekspluatÄcijas laika specifikÄcija. Å Ä« pasta pakalpojuma vajadzÄ«bÄm mezglam bÅ«s bezgalÄ«gs kalpoÅ”anas laiks, ko var pÄrtraukt, izmantojot SIGTERM
, savukÄrt echo klienta darbÄ«ba tiks pÄrtraukta pÄc konfigurÄtÄ ierobežotÄ ilguma. SkatÄ«t
KopÄjais attÄ«stÄ«bas process
ApskatÄ«sim, kÄ Å”Ä« pieeja maina veidu, kÄ mÄs strÄdÄjam ar konfigurÄciju.
KonfigurÄcija kÄ kods tiks apkopota un izveidos artefaktu. Å Ä·iet saprÄtÄ«gi nodalÄ«t konfigurÄcijas artefaktus no citiem koda artefaktiem. Bieži vien mums var bÅ«t daudz konfigurÄciju vienÄ koda bÄzÄ. Un, protams, mums var bÅ«t vairÄkas dažÄdu konfigurÄcijas atzaru versijas. KonfigurÄcijÄ mÄs varam atlasÄ«t noteiktas bibliotÄku versijas, un tas paliks nemainÄ«gs ikreiz, kad izvietosim Å”o konfigurÄciju.
KonfigurÄcijas maiÅa kļūst par koda maiÅu. TÄtad uz to bÅ«tu jÄattiecas vienÄ un tajÄ paÅ”Ä kvalitÄtes nodroÅ”inÄÅ”anas procesÄ:
Biļete -> PR -> pÄrskatÄ«Å”ana -> sapludinÄÅ”ana -> nepÄrtraukta integrÄcija -> nepÄrtraukta izvietoÅ”ana
Å Ädai pieejai ir Å”Ädas sekas:
- KonfigurÄcija ir saskaÅota konkrÄtas sistÄmas gadÄ«jumam. Å Ä·iet, ka nav iespÄjams izveidot nepareizu savienojumu starp mezgliem.
- Nav viegli mainÄ«t konfigurÄciju tikai vienÄ mezglÄ. Å Ä·iet nesaprÄtÄ«gi pieteikties un mainÄ«t dažus teksta failus. TÄdÄjÄdi konfigurÄcijas novirze kļūst mazÄk iespÄjama.
- Nelielas konfigurÄcijas izmaiÅas nav viegli veikt.
- LielÄkÄ daļa konfigurÄcijas izmaiÅu tiks veiktas vienÄ un tajÄ paÅ”Ä izstrÄdes procesÄ, un tÄs tiks pÄrskatÄ«tas.
Vai mums ir nepiecieÅ”ama atseviŔķa repozitorija ražoÅ”anas konfigurÄcijai? RažoÅ”anas konfigurÄcijÄ var bÅ«t ietverta sensitÄ«va informÄcija, kuru mÄs vÄlÄtos glabÄt daudziem cilvÄkiem nepieejamÄ vietÄ. TÄpÄc varÄtu bÅ«t vÄrts saglabÄt atseviŔķu repozitoriju ar ierobežotu piekļuvi, kurÄ bÅ«s ietverta ražoÅ”anas konfigurÄcija. MÄs varam sadalÄ«t konfigurÄciju divÄs daļÄs - vienÄ, kas satur visatvÄrtÄkos ražoÅ”anas parametrus, un tajÄ, kas satur slepeno konfigurÄcijas daļu. Tas ļautu lielÄkajai daļai izstrÄdÄtÄju piekļūt lielÄkajai daļai parametru, vienlaikus ierobežojot piekļuvi patieÅ”Äm jutÄ«gÄm lietÄm. To ir viegli paveikt, izmantojot starpposma pazÄ«mes ar noklusÄjuma parametru vÄrtÄ«bÄm.
VariÄcijas
ApskatÄ«sim piedÄvÄtÄs pieejas plusus un mÄ«nusus salÄ«dzinÄjumÄ ar citÄm konfigurÄcijas pÄrvaldÄ«bas metodÄm.
PirmkÄrt, mÄs uzskaitÄ«sim dažas alternatÄ«vas dažÄdiem piedÄvÄtÄ konfigurÄcijas veida aspektiem:
- Teksta fails mÄrÄ·a maŔīnÄ.
- CentralizÄta atslÄgu vÄrtÄ«bu krÄtuve (piemÄram,
etcd
/zookeeper
). - ApakÅ”procesa komponenti, kurus var pÄrkonfigurÄt/restartÄt bez procesa restartÄÅ”anas.
- KonfigurÄcija Ärpus artefaktu un versiju kontroles.
Teksta fails nodroÅ”ina zinÄmu elastÄ«bu ad-hoc labojumu ziÅÄ. SistÄmas administrators var pieteikties mÄrÄ·a mezglÄ, veikt izmaiÅas un vienkÄrÅ”i restartÄt pakalpojumu. Tas var nebÅ«t tik labs lielÄkÄm sistÄmÄm. Aiz pÄrmaiÅÄm nav atstÄtas nekÄdas pÄdas. IzmaiÅas nepÄrskata cits acu pÄris. Var bÅ«t grÅ«ti noskaidrot, kas ir izraisÄ«jis izmaiÅas. Tas nav pÄrbaudÄ«ts. No sadalÄ«tÄs sistÄmas viedokļa administrators var vienkÄrÅ”i aizmirst atjauninÄt konfigurÄciju vienÄ no citiem mezgliem.
(Btw, ja galu galÄ bÅ«s jÄsÄk lietot teksta konfigurÄcijas faili, mums bÅ«s jÄpievieno tikai parsÄtÄjs + validators, kas varÄtu radÄ«t to paÅ”u Config
ierakstiet un ar to pietiktu, lai sÄktu lietot teksta konfigurÄcijas. Tas arÄ« parÄda, ka kompilÄÅ”anas laika konfigurÄcijas sarežģītÄ«ba ir nedaudz mazÄka nekÄ teksta konfigurÄciju sarežģītÄ«ba, jo teksta versijÄ mums ir nepiecieÅ”ams papildu kods.)
CentralizÄta atslÄgu vÄrtÄ«bu krÄtuve ir labs mehÄnisms lietojumprogrammas meta parametru izplatÄ«Å”anai. Å eit mums ir jÄdomÄ par to, ko mÄs uzskatÄm par konfigurÄcijas vÄrtÄ«bÄm un kas ir tikai dati. Dota funkcija C => A => B
mÄs parasti saucam par reti mainÄ«gÄm vÄrtÄ«bÄm C
"konfigurÄcija", bet bieži mainÄ«ti dati A
- tikai ievadiet datus. KonfigurÄcija ir jÄnodroÅ”ina funkcijai agrÄk nekÄ dati A
. Å
emot vÄrÄ Å”o ideju, mÄs varam teikt, ka paredzamais izmaiÅu biežums var tikt izmantots, lai atŔķirtu konfigurÄcijas datus no tikai datiem. ArÄ« dati parasti nÄk no viena avota (lietotÄja), un konfigurÄcija nÄk no cita avota (administrators). Darbs ar parametriem, kurus var mainÄ«t pÄc inicializÄcijas procesa, palielina lietojumprogrammu sarežģītÄ«bu. Å Ädiem parametriem mums bÅ«s jÄapstrÄdÄ to piegÄdes mehÄnisms, parsÄÅ”ana un validÄcija, kÄ arÄ« nepareizu vÄrtÄ«bu apstrÄde. TÄpÄc, lai samazinÄtu programmas sarežģītÄ«bu, mÄs labÄk samazinÄm to parametru skaitu, kas var mainÄ«ties izpildes laikÄ (vai pat tos pilnÄ«bÄ likvidÄjam).
No Ŕīs ziÅas viedokļa mums vajadzÄtu noŔķirt statiskos un dinamiskos parametrus. Ja pakalpojumu loÄ£ika prasa retas dažu parametru izmaiÅas izpildes laikÄ, mÄs tos varam saukt par dinamiskiem parametriem. PretÄjÄ gadÄ«jumÄ tie ir statiski un var tikt konfigurÄti, izmantojot piedÄvÄto pieeju. Dinamiskajai pÄrkonfigurÄcijai var bÅ«t nepiecieÅ”amas citas pieejas. PiemÄram, sistÄmas daļas var restartÄt ar jaunajiem konfigurÄcijas parametriem lÄ«dzÄ«gi kÄ atseviŔķu sadalÄ«tas sistÄmas procesu restartÄÅ”ana.
(Mans pazemÄ«gais viedoklis ir izvairÄ«ties no izpildlaika pÄrkonfigurÄcijas, jo tas palielina sistÄmas sarežģītÄ«bu.
VarÄtu bÅ«t vienkÄrÅ”Äk paļauties uz OS atbalstu procesu restartÄÅ”anai. TomÄr tas ne vienmÄr var bÅ«t iespÄjams.)
Viens svarÄ«gs statiskÄs konfigurÄcijas izmantoÅ”anas aspekts, kas dažreiz liek cilvÄkiem apsvÄrt dinamisku konfigurÄciju (bez citiem iemesliem), ir pakalpojuma dÄ«kstÄve konfigurÄcijas atjauninÄÅ”anas laikÄ. PatieÅ”Äm, ja mums ir jÄveic izmaiÅas statiskajÄ konfigurÄcijÄ, mums ir jÄrestartÄ sistÄma, lai jaunas vÄrtÄ«bas kļūtu efektÄ«vas. PrasÄ«bas attiecÄ«bÄ uz dÄ«kstÄvi dažÄdÄm sistÄmÄm atŔķiras, tÄpÄc tas var nebÅ«t tik kritisks. Ja tas ir kritisks, mums ir iepriekÅ” jÄplÄno sistÄmas restartÄÅ”ana. PiemÄram, mÄs varÄtu Ä«stenot
KÄ ar konfigurÄcijas saglabÄÅ”anu artefakta versijÄ vai ÄrpusÄ? KonfigurÄcijas saglabÄÅ”ana artefaktÄ vairumÄ gadÄ«jumu nozÄ«mÄ, ka Ŕī konfigurÄcija ir izturÄjusi tÄdu paÅ”u kvalitÄtes nodroÅ”inÄÅ”anas procesu kÄ citi artefakti. TÄtad varÄtu bÅ«t pÄrliecinÄts, ka konfigurÄcija ir labas kvalitÄtes un uzticama. Gluži pretÄji, konfigurÄcija atseviÅ”Ä·Ä failÄ nozÄ«mÄ, ka nav izsekojamÄ«bas par to, kurÅ” un kÄpÄc veica izmaiÅas Å”ajÄ failÄ. Vai tas ir svarÄ«gi? MÄs uzskatÄm, ka lielÄkajai daļai ražoÅ”anas sistÄmu labÄk ir nodroÅ”inÄt stabilu un kvalitatÄ«vu konfigurÄciju.
Artefakta versija ļauj noskaidrot, kad tas tika izveidots, kÄdas vÄrtÄ«bas tas satur, kÄdas funkcijas ir iespÄjotas/atspÄjotas, kurÅ” bija atbildÄ«gs par katras izmaiÅas konfigurÄcijÄ. Lai saglabÄtu konfigurÄciju artefaktÄ, var bÅ«t vajadzÄ«gas zinÄmas pÅ«les, un tÄ ir dizaina izvÄle.
Plusi mīnusi
Å eit mÄs vÄlamies izcelt dažas piedÄvÄtÄs pieejas priekÅ”rocÄ«bas un apspriest dažus trÅ«kumus.
PriekŔrocības
PilnÄ«gas sadalÄ«tÄs sistÄmas kompilÄjamÄs konfigurÄcijas iezÄ«mes:
- StatiskÄ konfigurÄcijas pÄrbaude. Tas nodroÅ”ina augstu pÄrliecÄ«bas lÄ«meni, ka konfigurÄcija ir pareiza, Åemot vÄrÄ veida ierobežojumus.
- BagÄtÄ«ga konfigurÄcijas valoda. Parasti citas konfigurÄcijas pieejas aprobežojas ar maksimÄli mainÄ«gu aizstÄÅ”anu.
Izmantojot Scala, var izmantot plaÅ”u valodu funkciju klÄstu, lai uzlabotu konfigurÄciju. PiemÄram, mÄs varam izmantot iezÄ«mes, lai nodroÅ”inÄtu noklusÄjuma vÄrtÄ«bas, objektus, lai iestatÄ«tu dažÄdu darbÄ«bas jomu, mÄs varam atsaukties uz tiemval
s definÄts tikai vienu reizi ÄrÄjÄ tvÄrumÄ (DRY). Ir iespÄjams izmantot burtiskas secÄ«bas vai noteiktu klaÅ”u gadÄ«jumus (Seq
,Map
, Utt.) - DSL. Scala nodroÅ”ina pienÄcÄ«gu atbalstu DSL rakstÄ«tÄjiem. Å os lÄ«dzekļus var izmantot, lai izveidotu ÄrtÄku un lietotÄjam draudzÄ«gÄku konfigurÄcijas valodu, lai galÄ«gÄ konfigurÄcija bÅ«tu vismaz lasÄma domÄna lietotÄjiem.
- IntegritÄte un saskaÅotÄ«ba mezglos. Viena no priekÅ”rocÄ«bÄm, ko sniedz visas izplatÄ«tÄs sistÄmas konfigurÄcija vienuviet, ir tÄ, ka visas vÄrtÄ«bas tiek stingri noteiktas vienreiz un pÄc tam tiek izmantotas atkÄrtoti visÄs vietÄs, kur mums tÄs ir vajadzÄ«gas. ArÄ« droÅ”Ä porta deklarÄciju ierakstÄ«Å”ana nodroÅ”ina, ka visÄs iespÄjamÄs pareizajÄs konfigurÄcijÄs sistÄmas mezgli runÄs vienÄ valodÄ. Starp mezgliem ir skaidra atkarÄ«ba, kas apgrÅ«tina dažu pakalpojumu sniegÅ”anu.
- Augsta izmaiÅu kvalitÄte. KopÄjÄ pieeja konfigurÄcijas izmaiÅu nodoÅ”anai parastajÄ PR procesÄ nosaka augstus kvalitÄtes standartus arÄ« konfigurÄcijÄ.
- VienlaicÄ«gas konfigurÄcijas izmaiÅas. Ikreiz, kad veicam izmaiÅas konfigurÄcijÄ, automÄtiskÄ izvietoÅ”ana nodroÅ”ina, ka visi mezgli tiek atjauninÄti.
- Lietojumprogrammas vienkÄrÅ”oÅ”ana. Lietojumprogrammai nav jÄparsÄ un jÄapstiprina konfigurÄcija un jÄapstrÄdÄ nepareizas konfigurÄcijas vÄrtÄ«bas. Tas vienkÄrÅ”o vispÄrÄjo lietojumprogrammu. (Neliels sarežģītÄ«bas pieaugums ir paÅ”Ä konfigurÄcijÄ, taÄu tas ir apzinÄts kompromiss pret droŔību.) Atgriezties pie parastÄs konfigurÄcijas ir diezgan vienkÄrÅ”i ā vienkÄrÅ”i pievienojiet trÅ«kstoÅ”Äs daļas. VienkÄrÅ”Äk ir sÄkt ar apkopoto konfigurÄciju un atlikt papildu daļu ievieÅ”anu uz vÄlÄku laiku.
- VersionÄta konfigurÄcija. TÄ kÄ konfigurÄcijas izmaiÅas notiek vienÄ un tajÄ paÅ”Ä izstrÄdes procesÄ, mÄs iegÅ«stam artefaktu ar unikÄlu versiju. Tas ļauj mums pÄrslÄgt konfigurÄciju atpakaļ, ja nepiecieÅ”ams. MÄs pat varam izvietot konfigurÄciju, kas tika izmantota pirms gada, un tÄ darbosies tieÅ”i tÄpat. Stabila konfigurÄcija uzlabo sadalÄ«tÄs sistÄmas paredzamÄ«bu un uzticamÄ«bu. KonfigurÄcija tiek fiksÄta kompilÄÅ”anas laikÄ, un to nevar viegli mainÄ«t ražoÅ”anas sistÄmÄ.
- ModularitÄte. PiedÄvÄtÄ sistÄma ir modulÄra, un moduļus var kombinÄt dažÄdos veidos
atbalsta dažÄdas konfigurÄcijas (iestatÄ«jumus/izkÄrtojumus). Jo Ä«paÅ”i ir iespÄjams izmantot maza mÄroga viena mezgla izkÄrtojumu un liela mÄroga vairÄku mezglu iestatÄ«jumu. Ir saprÄtÄ«gi izmantot vairÄkus ražoÅ”anas izkÄrtojumus. - TestÄÅ”ana. TestÄÅ”anas nolÅ«kos var ieviest viltotu pakalpojumu un izmantot to kÄ atkarÄ«bu tipa droÅ”Ä veidÄ. Vienlaikus varÄtu uzturÄt dažus dažÄdus testÄÅ”anas izkÄrtojumus ar dažÄdÄm daļÄm, kas aizstÄtas ar izspÄles.
- IntegrÄcijas pÄrbaude. Dažreiz sadalÄ«tÄs sistÄmÄs ir grÅ«ti palaist integrÄcijas testus. Izmantojot aprakstÄ«to pieeju visas sadalÄ«tÄs sistÄmas droÅ”as konfigurÄcijas ievadÄ«Å”anai, mÄs varam kontrolÄt visas sadalÄ«tÄs daļas vienÄ serverÄ«. Ir viegli lÄ«dzinÄties situÄcijai
kad kÄds no pakalpojumiem kļūst nepieejams.
Trūkumi
ApkopotÄ konfigurÄcijas pieeja atŔķiras no āparastÄsā konfigurÄcijas, un tÄ var neatbilst visÄm vajadzÄ«bÄm. Å eit ir daži no kompilÄtÄs konfigurÄcijas trÅ«kumiem:
- StatiskÄ konfigurÄcija. Tas var nebÅ«t piemÄrots visÄm lietojumprogrammÄm. Dažos gadÄ«jumos ir nepiecieÅ”ams Ätri noteikt konfigurÄciju ražoÅ”anÄ, apejot visus droŔības pasÄkumus. Å Ä« pieeja padara to grÅ«tÄku. PÄc jebkÄdu konfigurÄcijas izmaiÅu veikÅ”anas ir nepiecieÅ”ama kompilÄcija un pÄrizvietoÅ”ana. TÄ ir gan iezÄ«me, gan slogs.
- KonfigurÄcijas Ä£enerÄÅ”ana. Ja konfigurÄciju Ä£enerÄ kÄds automatizÄcijas rÄ«ks, Å”ai pieejai ir nepiecieÅ”ama turpmÄka kompilÄcija (kas savukÄrt var neizdoties). IespÄjams, bÅ«s nepiecieÅ”amas papildu pÅ«les, lai integrÄtu Å”o papildu darbÄ«bu veidoÅ”anas sistÄmÄ.
- Instrumenti. MÅ«sdienÄs tiek izmantots daudz rÄ«ku, kas balstÄs uz teksta konfigurÄcijÄm. Daži no tiem
nebÅ«s piemÄrojams, kad tiks apkopota konfigurÄcija. - Ir nepiecieÅ”ama domÄÅ”anas veida maiÅa. IzstrÄdÄtÄji un DevOps ir pazÄ«stami ar teksta konfigurÄcijas failiem. Ideja par konfigurÄcijas apkopoÅ”anu viÅiem varÄtu Ŕķist dÄ«vaina.
- Pirms kompilÄjamÄs konfigurÄcijas ievieÅ”anas ir nepiecieÅ”ams augstas kvalitÄtes programmatÅ«ras izstrÄdes process.
Ieviestajam piemÄram ir daži ierobežojumi:
- Ja mÄs nodroÅ”inÄm papildu konfigurÄciju, kas nav nepiecieÅ”ama mezgla ievieÅ”anai, kompilators mums nepalÄ«dzÄs noteikt neesoÅ”u implementÄciju. To var atrisinÄt, izmantojot
HList
vai ADT (gadÄ«juma klases) mezgla konfigurÄcijai, nevis pazÄ«mÄm un kÅ«ka modelim. - Mums konfigurÄcijas failÄ ir jÄnodroÅ”ina daži parametri: (
package
,import
,object
deklarÄcijas;
override def
's parametriem, kuriem ir noklusÄjuma vÄrtÄ«bas). To var daļÄji atrisinÄt, izmantojot DSL. - Å ajÄ rakstÄ mÄs neaptveram lÄ«dzÄ«gu mezglu klasteru dinamisko pÄrkonfigurÄciju.
SecinÄjumi
Å ajÄ ziÅojumÄ mÄs esam apsprieduÅ”i ideju par konfigurÄcijas attÄloÅ”anu tieÅ”i avota kodÄ droÅ”Ä veidÄ. Å o pieeju varÄtu izmantot daudzÄs lietojumprogrammÄs, aizstÄjot xml un citas teksta konfigurÄcijas. Neskatoties uz to, ka mÅ«su piemÄrs ir ieviests programmÄ Scala, to var tulkot arÄ« citÄs kompilÄjamÄs valodÄs (piemÄram, Kotlin, C#, Swift utt.). Å o pieeju varÄtu izmÄÄ£inÄt jaunÄ projektÄ un gadÄ«jumÄ, ja tÄ neder, pÄriet uz vecmodÄ«go metodi.
Protams, kompilÄjamai konfigurÄcijai nepiecieÅ”ams augstas kvalitÄtes izstrÄdes process. SavukÄrt tas sola nodroÅ”inÄt tikpat augstas kvalitÄtes robustu konfigurÄciju.
Å o pieeju var paplaÅ”inÄt dažÄdos veidos:
- Var izmantot makro, lai veiktu konfigurÄcijas validÄciju un kompilÄÅ”anas laikÄ neizdodas, ja rodas jebkÄdas biznesa loÄ£ikas ierobežojumu kļūmes.
- DSL var ieviest, lai attÄlotu konfigurÄciju domÄnam lietotÄjam draudzÄ«gÄ veidÄ.
- Dinamiska resursu pÄrvaldÄ«ba ar automÄtiskÄm konfigurÄcijas korekcijÄm. PiemÄram, kad mÄs pielÄgojam klastera mezglu skaitu, mÄs varÄtu vÄlÄties, lai (1) mezgli iegÅ«tu nedaudz modificÄtu konfigurÄciju; (2) klasteru pÄrvaldnieks, lai saÅemtu informÄciju par jauniem mezgliem.
Paldies
Es vÄlos pateikties Andrejam Saksonovam, PÄvelam Popovam un Antonam Å ehaevam par iedvesmojoÅ”Äm atsauksmÄm par Ŕī ieraksta melnrakstu, kas man palÄ«dzÄja to padarÄ«t skaidrÄku.
Avots: www.habr.com