Компиляцияланган бөлүштүрүлгөн системанын конфигурациясы

Мен бөлүштүрүлгөн системанын конфигурациясы менен иштөөнүн бир кызыктуу механизмин айткым келет. Конфигурация коопсуз түрлөрүн колдонуу менен түз компиляцияланган тилде (Scala) көрсөтүлөт. Бул пост ушундай конфигурациянын мисалын берет жана жалпы иштеп чыгуу процессине компиляцияланган конфигурацияны киргизүүнүн ар кандай аспектилерин талкуулайт.

Компиляцияланган бөлүштүрүлгөн системанын конфигурациясы

(Англисче)

тааныштыруу

Ишенимдүү бөлүштүрүлгөн системаны түзүү бардык түйүндөр башка түйүндөр менен синхрондуу туура конфигурацияны колдонууну билдирет. DevOps технологиялары (terraform, ansible же ушуга окшош) адатта конфигурация файлдарын автоматтык түрдө түзүү үчүн колдонулат (көбүнчө ар бир түйүн үчүн өзгөчө). Биз ошондой эле бардык байланыш түйүндөрүнүн бирдей протоколдорду (анын ичинде ошол эле версиясын) колдонуп жаткандыгына ишенгибиз келет. Болбосо, биздин бөлүштүрүлгөн тутумубузга дал келбестик орнотулат. JVM дүйнөсүндө, бул талаптын бир натыйжасы протоколдук билдирүүлөрдү камтыган китепкананын бирдей версиясы бардык жерде колдонулушу керек.

Бөлүштүрүлгөн системаны сыноо жөнүндө эмне айтууга болот? Албетте, биз интеграциялык тестирлөөгө өтүүдөн мурун бардык компоненттерде бирдик тесттери бар деп ойлойбуз. (Тесттин натыйжаларын аткаруу убактысына экстраполяциялоо үчүн, биз тестирлөө этабында жана иштөө убагында бирдей китепканалар топтомун беришибиз керек.)

Интеграция тесттери менен иштегенде, бардык түйүндөрдө бардык жерде бирдей класс жолун колдонуу оңой. Болгону, ошол эле класс жолунун иштөө убагында колдонулушун камсыз кылуу керек. (Ар кандай класс жолдору менен ар кандай түйүндөрдү иштетүү толук мүмкүн болсо да, бул жалпы конфигурацияга татаалдаштырат жана жайылтуу жана интеграция сыноолорунда кыйынчылыктарды жаратат.) Бул посттун максаттары үчүн, биз бардык түйүндөр бир класс жолун колдонот деп ойлойбуз.

Конфигурация колдонмо менен өнүгөт. Биз программанын эволюциясынын ар кандай этаптарын аныктоо үчүн версияларды колдонобуз. Конфигурациялардын ар кандай версияларын аныктоо логикалык көрүнөт. Жана конфигурациянын өзүн версияны башкаруу системасына коюңуз. Эгерде өндүрүштө бир гана конфигурация бар болсо, анда биз жөн гана версия номерин колдоно алабыз. Эгерде биз көптөгөн өндүрүштүк инстанцияларды колдонсок, анда бизге бир нече керек болот
конфигурация бутактары жана версияга кошумча кошумча белги (мисалы, филиалдын аталышы). Ошентип, биз так конфигурацияны так аныктай алабыз. Ар бир конфигурация идентификатору уникалдуу түрдө бөлүштүрүлгөн түйүндөрдүн, порттордун, тышкы ресурстардын жана китепкананын версияларынын белгилүү бир айкалышына туура келет. Бул посттун максаттары үчүн биз бир гана бутак бар деп ойлойбуз жана конфигурацияны чекит менен бөлүнгөн үч санды колдонуу менен кадимки жол менен аныктай алабыз (1.2.3).

Заманбап шарттарда конфигурация файлдары сейрек кол менен түзүлөт. Көбүнчө алар жайылтуу учурунда түзүлөт жана мындан ары тийбейт (ошондуктан эч нерсени сындырба). Табигый суроо туулат: эмне үчүн биз конфигурацияны сактоо үчүн дагы эле текст форматын колдонобуз? Жашоочу альтернатива конфигурация үчүн кадимки кодду колдонуу жана компиляция убактысын текшерүүдөн пайда алуу мүмкүнчүлүгү окшойт.

Бул постто биз компиляцияланган артефакттын ичиндеги конфигурацияны көрсөтүү идеясын изилдейбиз.

Компиляцияланган конфигурация

Бул бөлүм статикалык компиляцияланган конфигурациянын мисалын берет. Эки жөнөкөй кызмат ишке ашырылат - echo кызматы жана жаңырык кызматы кардары. Бул эки кызматтын негизинде эки система опциясы чогулган. Бир вариантта эки кызмат тең бир түйүндө, башка вариантта ар кандай түйүндөрдө жайгашкан.

Адатта бөлүштүрүлгөн система бир нече түйүндөрдү камтыйт. Сиз кандайдыр бир түрдөгү маанилерди колдонуп түйүндөрдү аныктай аласыз NodeId:

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

же

case class NodeId(hostName: String)

же

object Singleton
type NodeId = Singleton.type

Түйүндөр ар кандай ролдорду аткарышат, алар кызматтарды иштетишет жана алардын ортосунда TCP/HTTP байланыштары түзүлүшү мүмкүн.

TCP байланышын сүрөттөө үчүн бизге жок дегенде порт номери керек. Кардар да, сервер да бир эле протоколду колдонуп жатканын камсыз кылуу үчүн ошол портто колдоого алынган протоколду чагылдыргыбыз келет. Биз төмөнкү классты колдонуу менен байланышты сүрөттөйбүз:

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

кайда Port - жөн гана бүтүн сан Int алгылыктуу маанилердин диапазонун көрсөтүү менен:

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

Такталган түрлөрү

Китепкананы көрүү тазаланган и менин билдирүү. Кыскача айтканда, китепкана компиляция убагында текшерилген түрлөргө чектөөлөрдү кошууга мүмкүндүк берет. Бул учурда, жарактуу порт номеринин маанилери 16 биттик бүтүн сандар. Компиляцияланган конфигурация үчүн такталган китепкананы колдонуу милдеттүү эмес, бирок компилятордун конфигурацияны текшерүү мүмкүнчүлүгүн жакшыртат.

HTTP (REST) ​​протоколдору үчүн, порт номеринен тышкары, бизге кызматка жол керек болушу мүмкүн:

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

Фантомдук түрлөрү

Протоколду компиляция убагында аныктоо үчүн биз класста колдонулбаган типтин параметрин колдонобуз. Бул чечим биз протоколдун инстанциясын аткаруу убагында колдонбогонубузга байланыштуу, бирок компилятор протоколдордун шайкештигин текшерүүсүн каалайбыз. Протоколду көрсөтүү менен, биз ылайыксыз кызматты көз карандылык катары өткөрө албайбыз.

Жалпы протоколдордун бири Json сериялаштыруу менен REST API болуп саналат:

sealed trait JsonHttpRestProtocol[RequestMessage, ResponseMessage]

кайда RequestMessage - суроо-талап түрү, ResponseMessage - жооп түрү.
Албетте, биз талап кылган сыпаттаманын тактыгын камсыз кылган башка протоколдук сүрөттөмөлөрдү колдоно алабыз.

Бул посттун максаттары үчүн биз протоколдун жөнөкөйлөштүрүлгөн версиясын колдонобуз:

sealed trait SimpleHttpGetRest[RequestMessage, ResponseMessage]

Бул жерде сурам urlге тиркелген сап болуп саналат жана жооп HTTP жооптун корпусундагы кайтарылган сап болуп саналат.

Кызматтын конфигурациясы кызматтын аталышы, порттору жана көз карандылыгы менен сүрөттөлөт. Бул элементтер Scala бир нече жол менен көрсөтүлүшү мүмкүн (мисалы, HList-s, алгебралык маалымат түрлөрү). Бул посттун максаттары үчүн биз торт үлгүсүн колдонобуз жана модулдарды колдонобуз trait's. (Торт үлгүсү бул ыкманын милдеттүү элементи эмес. Бул жөн гана мүмкүн болгон ишке ашыруу.)

Кызматтардын ортосундагы көз карандылык портторду кайтаруучу ыкмалар катары көрсөтүлүшү мүмкүн EndPointбашка түйүндөрдүн:

  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 кызматына көз карандылыкты жарыялайбыз:

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

Көз карандылык экспорттолгон кызмат менен бирдей echoService. Атап айтканда, echo кардарында биз ошол эле протоколду талап кылабыз. Ошондуктан, эки кызматты туташтырууда, биз бардыгы туура иштей тургандыгына ишенсек болот.

Кызмат көрсөтүүлөрдү ишке ашыруу

Кызматты баштоо жана токтотуу үчүн функция талап кылынат. (Кызматты токтотуу мүмкүнчүлүгү тестирлөө үчүн абдан маанилүү.) Дагы, мындай функцияны ишке ашыруу үчүн бир нече варианттар бар (мисалы, конфигурациянын түрүнө жараша тип класстарын колдонсок болот). Бул посттун максаттары үчүн биз торт үлгүсүн колдонобуз. Биз классты колдонуп кызматты көрсөтөбүз cats.Resource, анткени Бул класс мурунтан эле көйгөйлөр болгон учурда ресурстарды бошотууга кепилдик берүүчү каражаттарды камсыз кылат. Ресурсту алуу үчүн биз конфигурацияны жана даяр иштөө убактысынын контекстин камсыз кылышыбыз керек. Кызматты баштоо функциясы төмөнкүдөй болушу мүмкүн:

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

кайда

  • Config — бул кызматтын конфигурация түрү
  • AddressResolver — башка түйүндөрдүн даректерин билүүгө мүмкүндүк берүүчү аткаруу объекти (төмөндө караңыз)

жана китепканадан башка түрлөрү cats:

  • F[_] — эффекттин түрү (эң жөнөкөй учурда F[A] жөн гана функция болушу мүмкүн () => A. Бул постто биз колдонобуз cats.IO.)
  • Reader[A,B] - функциянын аздыр-көптүр синоними A => B
  • cats.Resource - алынуучу жана чыгарыла турган ресурс
  • Timer — таймер (бир аз уктап калууга жана убакыт аралыгын өлчөөгө мүмкүндүк берет)
  • ContextShift - окшоштук ExecutionContext
  • Applicative — жеке эффекттерди айкалыштырууга мүмкүндүк берген эффект түрүнүн классы (дээрлик монада). Татаал колдонмолордо аны колдонуу жакшыраак көрүнөт Monad/ConcurrentEffect.

Бул функция колду колдонуу менен биз бир нече кызматтарды ишке ашыра алабыз. Мисалы, эч нерсе кылбаган кызмат:

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

(См. Баштапкы код, башка кызматтар ишке ашырылган - эхо кызматы, echo кардары
и өмүр бою контроллерлор.)

Түйүн - бул бир нече кызматтарды ишке киргизе ала турган объект (ресурстардын чынжырын ишке киргизүү Cake Pattern менен камсыз кылынат):

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

Бул түйүн үчүн талап кылынган конфигурациянын так түрүн көрсөтүп жатканыбызды эске алыңыз. Эгер белгилүү бир кызмат талап кылган конфигурация түрлөрүнүн бирин көрсөтүүнү унутуп калсак, компиляция катасы пайда болот. Ошондой эле, биз бардык керектүү маалыматтар менен тиешелүү типтеги объектти камсыз кылмайынча, түйүндү баштай албайбыз.

Хосттун аталышынын чечими

Алыскы хостко туташуу үчүн бизге чыныгы IP дареги керек. Дарек конфигурациянын калган бөлүгүнө караганда кечирээк белгилүү болушу мүмкүн. Ошентип, бизге түйүн идентификаторун дарекке салыштыруучу функция керек:

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

Бул функцияны ишке ашыруунун бир нече жолу бар:

  1. Эгерде даректер бизге жайылтуудан мурун белгилүү болсо, анда биз Scala кодун түзө алабыз
    даректерди таап, андан кийин курууну иштетиңиз. Бул тесттерди түзүп, иштетет.
    Бул учурда, функция статикалык түрдө белгилүү болот жана коддо карта катары көрсөтүлүшү мүмкүн Map[NodeId, NodeAddress].
  2. Кээ бир учурларда, иш жүзүндөгү дарек түйүн башталгандан кийин гана белгилүү болот.
    Бул учурда, биз башка түйүндөрдүн алдында иштеген “ачуу кызматын” ишке ашыра алабыз жана бардык түйүндөр бул кызматка катталып, башка түйүндөрдүн даректерин сурайт.
  3. Эгер биз өзгөртө алсак /etc/hosts, анда сиз алдын ала аныкталган хост аттарын колдоно аласыз (мисалы my-project-main-node и echo-backend) жана жөн гана бул ысымдарды байланыштырыңыз
    жайгаштыруу учурунда IP даректери менен.

Бул постто биз бул иштерди кененирээк карап чыкпайбыз. Биздин үчүн
оюнчук мисалында, бардык түйүндөр бирдей IP дарекке ээ болот - 127.0.0.1.

Андан кийин, бөлүштүрүлгөн системанын эки вариантын карап чыгабыз:

  1. Бардык кызматтарды бир түйүнгө жайгаштыруу.
  2. Жана ар кандай түйүндөрдө эхо кызматын жана жаңырык кардарын хостинг.

үчүн конфигурация бир түйүн:

Жалгыз түйүн конфигурациясы

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

Объект кардардын да, сервердин да конфигурациясын ишке ашырат. Жашоо үчүн убакыт конфигурациясы да интервалдан кийин колдонулат lifetime программаны токтотуу. (Ctrl-C да иштейт жана бардык ресурстарды бошотот.)

турган системаны түзүү үчүн бирдей конфигурациялоо жана ишке ашыруу белгилеринин жыйындысы колдонулушу мүмкүн эки өзүнчө түйүн:

Эки түйүн конфигурациясы

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

Маанилүү! Кызматтар кандайча байланышканына көңүл буруңуз. Биз бир түйүн тарабынан ишке ашырылган кызматты башка түйүндүн көз карандылык ыкмасын ишке ашыруу катары белгилейбиз. Көз карандылык түрү компилятор тарабынан текшерилет, анткени протокол түрүн камтыйт. Ишке киргенде, көз карандылык туура максаттуу түйүн идентификаторун камтыйт. Бул схеманын аркасында биз порттун номерин так бир жолу көрсөтөбүз жана ар дайым туура портко кайрылууга кепилдик беребиз.

Эки системалык түйүндөрдү ишке ашыруу

Бул конфигурация үчүн биз бир эле кызматтын ишке ашырууларын өзгөртүүсүз колдонобуз. Бир гана айырмасы, азыр бизде ар кандай кызматтардын топтомун ишке ашырган эки объект бар:

  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
  }

Биринчи түйүн серверди ишке ашырат жана сервер конфигурациясын гана талап кылат. Экинчи түйүн кардарды ишке ашырат жана конфигурациянын башка бөлүгүн колдонот. Ошондой эле эки түйүн өмүр бою башкарууга муктаж. Сервер түйүнү токтогонго чейин чексиз иштейт SIGTERM'om, жана кардар түйүнү бир нече убакыттан кийин токтойт. См. ишке киргизгич колдонмо.

Жалпы өнүгүү процесси

Келгиле, бул конфигурация ыкмасы жалпы иштеп чыгуу процессине кандай таасир этээрин карап көрөлү.

Конфигурация коддун калган бөлүгү менен бирге түзүлөт жана артефакт (.jar) түзүлөт. Конфигурацияны өзүнчө артефактка коюунун мааниси бар окшойт. Себеби, биз бир эле коддун негизинде бир нече конфигурацияга ээ болушубуз мүмкүн. Дагы, ар кандай конфигурация бутактарына туура келген артефакттарды жаратууга болот. Китепканалардын белгилүү версияларына болгон көз карандылыктар конфигурация менен бирге сакталат жана бул версиялар конфигурациянын ошол версиясын колдонууну чечкен сайын түбөлүккө сакталат.

Ар кандай конфигурация өзгөртүү коддун өзгөрүшүнө айланат. Ошентип, ар бир
өзгөртүү кадимки сапатты камсыздоо процесси менен камтылат:

Мүчүлүштүктөрдү көзөмөлдөөчү билет -> PR -> карап чыгуу -> тиешелүү бутактар ​​менен бириктирүү ->
интеграция -> жайылтуу

Компиляцияланган конфигурацияны ишке ашыруунун негизги натыйжалары болуп төмөнкүлөр саналат:

  1. Конфигурация бөлүштүрүлгөн системанын бардык түйүндөрүндө ырааттуу болот. Бардык түйүндөр бир булактан бирдей конфигурацияны алгандыктан.

  2. Түйүндөрдүн биринде гана конфигурацияны өзгөртүү көйгөйлүү. Ошондуктан, "конфигурациянын дрейфи" күмөн.

  3. Конфигурацияга кичине өзгөртүүлөрдү киргизүү кыйындайт.

  4. Көпчүлүк конфигурация өзгөртүүлөрү жалпы иштеп чыгуу процессинин бир бөлүгү катары ишке ашат жана кайра каралууга тийиш.

Өндүрүш конфигурациясын сактоо үчүн мага өзүнчө репозиторий керекпи? Бул конфигурация сырсөздөрдү жана биз кирүү мүмкүнчүлүгүн чектеген башка купуя маалыматты камтышы мүмкүн. Ушуга таянып, акыркы конфигурацияны өзүнчө репозиторийде сактоонун мааниси бар окшойт. Сиз конфигурацияны эки бөлүккө бөлсөңүз болот: бири жалпыга жеткиликтүү конфигурация орнотууларын жана экинчиси чектелген орнотууларды камтыган. Бул көпчүлүк иштеп чыгуучуларга жалпы орнотууларга мүмкүнчүлүк берет. Бул бөлүү демейки маанилерди камтыган ортоңку белгилерди колдонуу менен оңой жетишилет.

Мүмкүн болгон вариациялар

Келгиле, түзүлгөн конфигурацияны кээ бир жалпы альтернативалар менен салыштырууга аракет кылалы:

  1. Максаттуу машинадагы текст файлы.
  2. Борборлоштурулган ачкыч-нарк дүкөнү (etcd/zookeeper).
  3. Процессти кайра баштабастан кайра конфигурациялоого/кайра баштоого боло турган процесс компоненттери.
  4. Конфигурацияны артефакт жана версия башкаруусунан тышкары сактоо.

Текст файлдары майда өзгөртүүлөр боюнча олуттуу ийкемдүүлүктү камсыз кылат. Системанын администратору алыскы түйүнгө кирип, тиешелүү файлдарга өзгөртүүлөрдү киргизип, кызматты кайра иштете алат. Ал эми чоң системалар үчүн мындай ийкемдүүлүк каалабашы мүмкүн. Киргизилген өзгөртүүлөр башка системаларда эч кандай из калтырбайт. Өзгөртүүлөрдү эч ким караган жок. Өзгөртүүлөрдү так ким жана эмне себептен киргизгенин аныктоо кыйын. Өзгөрүүлөр текшерилбейт. Эгерде система бөлүштүрүлгөн болсо, анда администратор башка түйүндөргө тиешелүү өзгөртүү киргизүүнү унутуп коюшу мүмкүн.

(Ошондой эле белгилей кетчү нерсе, компиляцияланган конфигурацияны колдонуу келечекте текст файлдарын колдонуу мүмкүнчүлүгүн жаап салбайт. Чыгаруу менен бирдей типти чыгарган талдоочу жана валидаторду кошуу жетиштүү болот. Config, жана сиз текст файлдарын колдоно аласыз. Мындан дароо эле компиляцияланган конфигурациялуу системанын татаалдыгы тексттик файлдарды колдонгон системанын татаалдыгынан бир аз азыраак болот, анткени текст файлдары кошумча кодду талап кылат.)

Борборлоштурулган ачкыч-нарк дүкөнү бөлүштүрүлгөн колдонмонун мета параметрлерин жайылтуу үчүн жакшы механизм болуп саналат. Биз конфигурация параметрлери деген эмне жана жөн гана маалымат эмне экенин чечишибиз керек. Бизге функция болсун C => A => B, жана параметрлери C сейрек өзгөрүүлөр, жана маалыматтар A - көп учурда. Бул учурда биз муну айта алабыз C - конфигурация параметрлери, жана A - маалыматтар. Көрүнүп тургандай, конфигурация параметрлери маалыматтардан айырмаланат, анткени алар жалпысынан маалыматтарга караганда азыраак өзгөрөт. Ошондой эле, маалымат адатта бир булактан (колдонуучудан), ал эми конфигурация параметрлери башкасынан (системанын администраторунан) келет.

Эгерде сейрек өзгөрүп турган параметрлерди программаны кайра баштабастан жаңылоо керек болсо, анда бул көп учурда программанын татаалдашына алып келиши мүмкүн, анткени биз кандайдыр бир жол менен параметрлерди жеткирүү, сактоо, талдоо жана текшерүү, жана туура эмес маанилерди иштетүү керек болот. Ошондуктан, программанын татаалдыгын азайтуу көз карашынан алганда, программанын иштөө учурунда өзгөрүшү мүмкүн болгон (же мындай параметрлерди такыр колдобой турган) параметрлердин санын азайтуу мааниси бар.

Бул посттун максаттары үчүн биз статикалык жана динамикалык параметрлерди айырмалайбыз. Эгерде кызматтын логикасы программанын иштешинде параметрлерди өзгөртүүнү талап кылса, анда мындай параметрлерди динамикалык деп атайбыз. Болбосо, параметрлер статикалык болуп саналат жана түзүлгөн конфигурацияны колдонуу менен конфигурациялоого болот. Динамикалык кайра конфигурациялоо үчүн, операциялык тутум процесстеринин кайра иштетилгенине окшош жаңы параметрлер менен программанын бөлүктөрүн кайра баштоо механизми керек болушу мүмкүн. (Биздин оюбузча, реалдуу убакыт режиминде реконфигурациялоодон качуу сунушталат, анткени бул системанын татаалдыгын жогорулатат. Мүмкүн болсо, процесстерди кайра баштоо үчүн ОСтун стандарттык мүмкүнчүлүктөрүн колдонуу жакшы.)

Адамдарды динамикалык кайра конфигурациялоону ойлондурган статикалык конфигурацияны колдонуунун маанилүү аспектиси - бул конфигурация жаңыртылгандан кийин системаны кайра жүктөө үчүн талап кылынган убакыт (токтоп туруу). Чынында, биз статикалык конфигурацияга өзгөртүүлөрдү киргизүү керек болсо, жаңы баалуулуктар күчүнө кириши үчүн системаны кайра иштетүүбүз керек болот. Ар кандай системалар үчүн токтоп калуу көйгөйүнүн оордугу ар кандай. Кээ бир учурларда, сиз жүктөө минималдуу болгон убакта кайра жүктөөнү пландаштырсаңыз болот. Эгер сиз үзгүлтүксүз кызмат көрсөтүү керек болсо, анда сиз ишке ашыра аласыз AWS ELB байланышын өчүрүү. Ошол эле учурда, системаны кайра жүктөө керек болгондо, биз бул системанын параллелдүү инстанциясын ишке киргизип, ага балансизаторду которуштуруп, эски байланыштардын бүтүшүн күтөбүз. Бардык эски байланыштар токтотулгандан кийин, биз системанын эски инстанциясын өчүрөбүз.

Эми конфигурацияны артефакттын ичинде же сыртында сактоо маселесин карап көрөлү. Эгерде биз конфигурацияны артефакттын ичинде сактасак, анда бизде жок дегенде артефактты чогултуу учурунда конфигурациянын тууралыгын текшерүү мүмкүнчүлүгүнө ээ болдук. Эгерде конфигурация башкарылуучу артефакттан тышкары болсо, бул файлга ким жана эмне үчүн өзгөртүүлөрдү киргизгендигин көзөмөлдөө кыйын. Бул канчалык маанилүү? Биздин оюбузча, көптөгөн өндүрүш системалары үчүн туруктуу жана сапаттуу конфигурацияга ээ болуу маанилүү.

Артефакттын версиясы анын качан түзүлгөнүн, кандай баалуулуктарды камтыганын, кандай функциялар иштетилген/өчүрүлгөнүн жана конфигурациядагы ар кандай өзгөртүүлөр үчүн ким жооптуу экенин аныктоого мүмкүндүк берет. Албетте, конфигурацияны артефакттын ичинде сактоо бир аз күч-аракетти талап кылат, андыктан негиздүү чечим кабыл алышыңыз керек.

Макул жана каршы

Мен сунуш кылынган технологиянын оң жана терс жактарына токтолгум келет.

артыкчылыктары

Төмөндө компиляцияланган бөлүштүрүлгөн системанын конфигурациясынын негизги өзгөчөлүктөрүнүн тизмеси келтирилген:

  1. Статикалык конфигурацияны текшерүү. Буга ишенүүгө мүмкүндүк берет
    конфигурация туура.
  2. Бай конфигурация тили. Эреже катары, башка конфигурация ыкмалары эң көп сап өзгөрмө алмаштыруу менен чектелет. Scala колдонуп жатканда, конфигурацияңызды жакшыртуу үчүн тилдин кеңири спектри бар. Мисалы, биз колдоно алабыз
    Параметрлерди топтоо үчүн объекттерди колдонуу менен демейки маанилер үчүн мүнөздөмөлөрдү колдонуу менен, биз тиркелген чөйрөдө бир гана жолу жарыяланган валдарга (КУРУ) кайрыла алабыз. Сиз конфигурациянын ичинде каалаган класстарды түзө аласыз (Seq, Map, жеке класстар).
  3. DSL. Scala DSL түзүүнү жеңилдеткен бир катар тил өзгөчөлүктөрүнө ээ. Бул мүмкүнчүлүктөрдү пайдаланып, конфигурацияны жок дегенде домен эксперттери окуй тургандай кылып, колдонуучулардын максаттуу тобу үчүн ыңгайлуу болгон конфигурация тилин ишке ашырууга болот. Адистер, мисалы, конфигурацияны карап чыгуу процессине катыша алышат.
  4. Түйүндөр ортосундагы бүтүндүк жана синхрондуулук. Бүтүндөй бөлүштүрүлгөн тутумдун конфигурациясынын бир чекитте сакталышынын артыкчылыктарынын бири - бардык баалуулуктар так бир жолу жарыяланып, андан кийин алар керек болгон жерде кайра колдонулат. Портторду жарыялоо үчүн фантом түрлөрүн колдонуу түйүндөрдүн бардык туура система конфигурацияларында туура келген протоколдорду колдонуп жатканын камсыздайт. Түйүндөр ортосунда ачык-айкын милдеттүү көз карандылыкка ээ болуу бардык кызматтардын туташкандыгын камсыздайт.
  5. Жогорку сапаттагы өзгөрүүлөр. Жалпы иштеп чыгуу процессин колдонуу менен конфигурацияга өзгөртүүлөрдү киргизүү конфигурациянын жогорку сапат стандарттарына да жетишүүгө мүмкүндүк берет.
  6. Бир убакта конфигурацияны жаңыртуу. Конфигурацияны өзгөрткөндөн кийин автоматтык системаны жайылтуу бардык түйүндөр жаңыртылганын камсыздайт.
  7. Колдонмону жөнөкөйлөтүү. Колдонмого талдоо, конфигурацияны текшерүү же туура эмес маанилерди иштетүү талап кылынбайт. Бул колдонмонун татаалдыгын азайтат. (Биздин мисалда байкалган конфигурациянын татаалдыгынын кээ бирлери компиляцияланган конфигурациянын атрибуту эмес, болгону көбүрөөк типтеги коопсуздукту камсыз кылуу каалоосунан келип чыккан аң-сезимдүү чечим.) Кадимки конфигурацияга кайтуу оңой - жетишпегенди гана ишке ашырыңыз. бөлүктөр. Демек, сиз, мисалы, керексиз бөлүктөрдү ишке ашырууну чындап керек болгон убакка чейин кийинкиге калтырып, түзүлгөн конфигурациядан баштасаңыз болот.
  8. Версификацияланган конфигурация. Конфигурациянын өзгөрүүлөрү башка өзгөрүүлөрдүн кадимки тагдырына ылайык келгендиктен, биз алган жыйынтык уникалдуу версиясы бар артефакт болуп саналат. Бул, мисалы, зарыл болсо, конфигурациянын мурунку версиясына кайтууга мүмкүндүк берет. Биз атүгүл бир жыл мурунку конфигурацияны колдоно алабыз жана система так ошондой иштейт. Туруктуу конфигурация бөлүштүрүлгөн системанын болжолдоо жана ишенимдүүлүгүн жакшыртат. Конфигурация компиляция стадиясында бекитилгендиктен, аны өндүрүштө жасалмалоо өтө кыйын.
  9. Модулдуулук. Сунушталган негиз модулдук болуп саналат жана модулдар ар кандай системаларды түзүү үчүн ар кандай жолдор менен айкалыштырылышы мүмкүн. Тактап айтканда, сиз системаны бир вариантта бир түйүндө, башкасында бир нече түйүндө иштетүүгө конфигурациялай аласыз. Системанын өндүрүштүк инстанциялары үчүн бир нече конфигурацияларды түзө аласыз.
  10. Сыноо. Жеке кызматтарды жасалма объекттер менен алмаштыруу менен сиз системанын тестирлөө үчүн ыңгайлуу болгон бир нече версиясын ала аласыз.
  11. Интеграция тести. Бүткүл бөлүштүрүлгөн система үчүн бирдиктүү конфигурацияга ээ болуу интеграциялык тестирлөөнүн бир бөлүгү катары бардык компоненттерди башкарылуучу чөйрөдө иштетүүгө мүмкүндүк берет. Мисалы, кээ бир түйүндөр жеткиликтүү болуп калган кырдаалды туурап алуу оңой.

Кемчиликтери жана чектөөлөрү

Компиляцияланган конфигурация башка конфигурация ыкмаларынан айырмаланат жана кээ бир колдонмолор үчүн ылайыктуу эмес. Төмөндө кээ бир кемчиликтер бар:

  1. Статикалык конфигурация. Кээде бардык коргоо механизмдерин айланып өтүп, өндүрүштүн конфигурациясын тез арада оңдоо керек. Бул ыкма менен ал кыйыныраак болушу мүмкүн. Жок дегенде, компиляция жана автоматтык жайылтуу дагы деле талап кылынат. Бул ыкманын пайдалуу өзгөчөлүгү жана кээ бир учурларда кемчилиги болуп саналат.
  2. Конфигурацияны түзүү. Эгер конфигурация файлы автоматтык курал тарабынан түзүлсө, куруу скриптин интеграциялоо үчүн кошумча күч-аракет талап кылынышы мүмкүн.
  3. Куралдар. Учурда конфигурация менен иштөөгө арналган утилиталар жана техникалар тексттик файлдарга негизделген. Мындай бардык утилиталар/техникалар компиляцияланган конфигурацияда жеткиликтүү болбойт.
  4. Көз карашты өзгөртүү талап кылынат. Иштеп чыгуучулар жана DevOps тексттик файлдарга көнүп калышкан. Конфигурацияны түзүү идеясы бир аз күтүлбөгөн жана адаттан тыш болуп, четке кагылышы мүмкүн.
  5. Жогорку сапаттагы иштеп чыгуу процесси талап кылынат. Компиляцияланган конфигурацияны ыңгайлуу колдонуу үчүн тиркемени түзүү жана жайылтуу процессин (CI/CD) толук автоматташтыруу зарыл. Болбосо, бул абдан ыңгайсыз болуп калат.

Келгиле, ошондой эле түзүлгөн конфигурация идеясы менен байланышпаган каралып жаткан мисалдын бир катар чектөөлөрүнө токтоло кетели:

  1. Эгерде биз түйүн тарабынан колдонулбаган керексиз конфигурация маалыматын берсек, анда компилятор жетишпеген ишке ашырууну аныктоого жардам бербейт. Бул көйгөйдү Торт үлгүсүнөн баш тартып, катаал түрлөрүн колдонуу менен чечсе болот, мисалы, HList же конфигурацияны көрсөтүү үчүн алгебралык маалымат түрлөрү (кейс класстары).
  2. Конфигурация файлында конфигурациянын өзүнө тиешеси жок сызыктар бар: (package, import,объекттердин декларациялары; override def's демейки маанилери бар параметрлер үчүн). Эгер сиз өз DSLиңизди ишке ашырсаңыз, бул жарым-жартылай болтурбай коюуга болот. Мындан тышкары, конфигурациянын башка түрлөрү да (мисалы, XML) файл структурасына белгилүү чектөөлөрдү киргизет.
  3. Бул посттун максаттары үчүн биз окшош түйүндөрдүн кластерин динамикалык кайра конфигурациялоону карап жаткан жокпуз.

жыйынтыктоо

Бул постто биз Scala тибиндеги системанын өркүндөтүлгөн мүмкүнчүлүктөрүн колдонуу менен баштапкы коддогу конфигурацияны көрсөтүү идеясын изилдедик. Бул ыкма ар кандай тиркемелерде xml же текст файлдарына негизделген салттуу конфигурация ыкмаларын алмаштыруу катары колдонулушу мүмкүн. Биздин мисал Scala-да ишке ашырылганына карабастан, ошол эле идеяларды башка компиляцияланган тилдерге (мисалы, Kotlin, C#, Swift, ...) өткөрүп берүүгө болот. Сиз бул ыкманы төмөнкү долбоорлордун биринде сынап көрүңүз, эгер ал иштебесе, жетишпеген бөлүктөрдү кошуп, текст файлына өтүңүз.

Албетте, компиляцияланган конфигурация жогорку сапаттагы иштеп чыгуу процессин талап кылат. Өз кезегинде конфигурациялардын жогорку сапаты жана ишенимдүүлүгү камсыз кылынат.

Каралган ыкманы кеңейтүүгө болот:

  1. Сиз компиляция убактысын текшерүү үчүн макросторду колдоно аласыз.
  2. Сиз конфигурацияны акыркы колдонуучуларга жеткиликтүү кылып көрсөтүү үчүн DSLди ишке ашыра аласыз.
  3. Сиз конфигурацияны автоматтык жөнгө салуу менен динамикалык ресурстарды башкарууну ишке ашыра аласыз. Мисалы, кластердеги түйүндөрдүн санын өзгөртүү (1) ар бир түйүн бир аз башкача конфигурацияны алуусун талап кылат; (2) кластер менеджери жаңы түйүндөр жөнүндө маалымат алды.

Ыракматтар

Мен Андрей Саксоновго, Павел Поповго жана Антон Нехаевге долбоордун статьясына конструктивдүү сын айтканы үчүн ыраазычылык билдирем.

Source: www.habr.com

Комментарий кошуу