JUnit ื‘- GitLab CI ืขื Kubernetes

ืœืžืจื•ืช ื”ืขื•ื‘ื“ื” ืฉื›ื•ืœื ื™ื•ื“ืขื™ื ื”ื™ื˜ื‘ ืฉื‘ื“ื™ืงืช ื”ืชื•ื›ื ื” ืฉืœืš ื”ื™ื ื—ืฉื•ื‘ื” ื•ื”ื›ืจื—ื™ืช, ื•ืจื‘ื™ื ืขื•ืฉื™ื ื–ืืช ืื•ื˜ื•ืžื˜ื™ืช ื›ื‘ืจ ื–ืžืŸ ืจื‘, ื‘ืžืจื—ื‘ ื”ืขืฆื•ื ืฉืœ ื”ื‘ืจ ืœื ื”ื™ื” ืžืชื›ื•ืŸ ืื—ื“ ืœื”ืงืžืช ืฉื™ืœื•ื‘ ืฉืœ ืžื•ืฆืจื™ื ืคื•ืคื•ืœืจื™ื™ื ื›ืœ ื›ืš ื‘ ื”ื ื™ืฉื” ื”ื–ื• ื›ืžื• (ื”ืื”ื•ื‘ื” ืขืœื™ื ื•) GitLab ื•-JUnit. ื‘ื•ืื• ื ืžืœื ืืช ื”ื—ืกืจ ื”ื–ื”!

JUnit ื‘- GitLab CI ืขื Kubernetes

ืึฒืงึธื“ึตืžึทืึดื™

ืจืืฉื™ืช, ื”ืจืฉื” ืœื™ ืœืชืช ืงืฆืช ื”ืงืฉืจ:

  • ืžื›ื™ื•ื•ืŸ ืฉื›ืœ ื”ืืคืœื™ืงืฆื™ื•ืช ืฉืœื ื• ืคื•ืขืœื•ืช ืขืœ Kubernetes, ื ืฉืงื•ืœ ืœื”ืคืขื™ืœ ื‘ื“ื™ืงื•ืช ืขืœ ื”ืชืฉืชื™ืช ื”ืžืชืื™ืžื”.
  • ืœื”ืจื›ื‘ื” ื•ืคืจื™ืกื” ืื ื• ืžืฉืชืžืฉื™ื werf (ืžื‘ื—ื™ื ืช ืจื›ื™ื‘ื™ ืชืฉืชื™ืช, ื–ื” ื’ื ืื•ืžืจ ืื•ื˜ื•ืžื˜ื™ืช ืฉื”ืœื ืžืขื•ืจื‘).
  • ืœื ืื›ื ืก ืœืคืจื˜ื™ ื™ืฆื™ืจืช ื”ื‘ื“ื™ืงื•ืช ื‘ืคื•ืขืœ: ื‘ืžืงืจื” ืฉืœื ื• ื”ืœืงื•ื— ื›ื•ืชื‘ ืืช ื”ื‘ื“ื™ืงื•ืช ื‘ืขืฆืžื•, ื•ืื ื• ืจืง ืžื‘ื˜ื™ื—ื™ื ืืช ื”ืฉืงืชืŸ (ื•ื ื•ื›ื—ื•ืช ื“ื™ื•ื•ื— ืžืงื‘ื™ืœ ื‘ื‘ืงืฉืช ื”ืžื™ื–ื•ื’).


ืื™ืš ื™ื™ืจืื” ืจืฆืฃ ื”ืคืขื•ืœื•ืช ื”ื›ืœืœื™?

  1. ื‘ื ื™ื™ืช ื”ืืคืœื™ืงืฆื™ื” โ€“ ื ืฉืžื™ื˜ ืืช ืชื™ืื•ืจ ื”ืฉืœื‘ ื”ื–ื”.
  2. ืคืจื•ืก ืืช ื”ื™ื™ืฉื•ื ืœืžืจื—ื‘ ืฉืžื•ืช ื ืคืจื“ ืฉืœ ืืฉื›ื•ืœ Kubernetes ื•ื”ืชื—ืœ ื‘ื‘ื“ื™ืงื”.
  3. ื—ื™ืคื•ืฉ ื—ืคืฆื™ื ื•ื ื™ืชื•ื— ื“ื•ื—ื•ืช JUnit ืขื GitLab.
  4. ืžื—ื™ืงืช ืžืจื—ื‘ ืฉืžื•ืช ืฉื ื•ืฆืจ ื‘ืขื‘ืจ.

ืขื›ืฉื™ื• - ืœื™ื™ืฉื•ื!

ื”ืชืืžื”

GitLab CI

ื ืชื—ื™ืœ ืขื ืงื˜ืข .gitlab-ci.yaml, ื”ืžืชืืจ ืืช ืคืจื™ืกืช ื”ืืคืœื™ืงืฆื™ื” ื•ื”ืคืขืœืช ื‘ื“ื™ืงื•ืช. ื”ืจืฉื™ืžื” ื”ืชื‘ืจืจื” ื›ื ืคื—ื™ืช ืœืžื“ื™, ืื– ื”ื™ื ื ื•ืกืคื” ื‘ื™ืกื•ื“ื™ื•ืช ืขื ื”ืขืจื•ืช:

variables:
# ะพะฑัŠัะฒะปัะตะผ ะฒะตั€ัะธัŽ werf, ะบะพั‚ะพั€ัƒัŽ ัะพะฑะธั€ะฐะตะผัั ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ
  WERF_VERSION: "1.0 beta"

.base_deploy: &base_deploy
  script:
# ัะพะทะดะฐะตะผ namespace ะฒ K8s, ะตัะปะธ ะตะณะพ ะฝะตั‚
    - kubectl --context="${WERF_KUBE_CONTEXT}" get ns ${CI_ENVIRONMENT_SLUG} || kubectl create ns ${CI_ENVIRONMENT_SLUG}
# ะทะฐะณั€ัƒะถะฐะตะผ werf ะธ ะดะตะฟะปะพะธะผ โ€” ะฟะพะดั€ะพะฑะฝะตะต ะพะฑ ัั‚ะพะผ ัะผ. ะฒ ะดะพะบัƒะผะตะฝั‚ะฐั†ะธะธ
# (https://werf.io/how_to/gitlab_ci_cd_integration.html#deploy-stage)
    - type multiwerf && source <(multiwerf use ${WERF_VERSION})
    - werf version
    - type werf && source <(werf ci-env gitlab --tagging-strategy tag-or-branch --verbose)
    - werf deploy --stages-storage :local
      --namespace ${CI_ENVIRONMENT_SLUG}
      --set "global.commit_ref_slug=${CI_COMMIT_REF_SLUG:-''}"
# ะฟะตั€ะตะดะฐะตะผ ะฟะตั€ะตะผะตะฝะฝัƒัŽ `run_tests`
# ะพะฝะฐ ะฑัƒะดะตั‚ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒัั ะฒ ั€ะตะฝะดะตั€ะต Helm-ั€ะตะปะธะทะฐ
      --set "global.run_tests=${RUN_TESTS:-no}"
      --set "global.env=${CI_ENVIRONMENT_SLUG}"
# ะธะทะผะตะฝัะตะผ timeout (ะฑั‹ะฒะฐัŽั‚ ะดะพะปะณะธะต ั‚ะตัั‚ั‹) ะธ ะฟะตั€ะตะดะฐะตะผ ะตะณะพ ะฒ ั€ะตะปะธะท
      --set "global.ci_timeout=${CI_TIMEOUT:-900}"
     --timeout ${CI_TIMEOUT:-900}
  dependencies:
    - Build

.test-base: &test-base
  extends: .base_deploy
  before_script:
# ัะพะทะดะฐะตะผ ะดะธั€ะตะบั‚ะพั€ะธัŽ ะดะปั ะฑัƒะดัƒั‰ะตะณะพ ะพั‚ั‡ะตั‚ะฐ, ะธัั…ะพะดั ะธะท $CI_COMMIT_REF_SLUG
    - mkdir /mnt/tests/${CI_COMMIT_REF_SLUG} || true
# ะฒั‹ะฝัƒะถะดะตะฝะฝั‹ะน ะบะพัั‚ั‹ะปัŒ, ั‚.ะบ. GitLab ั…ะพั‡ะตั‚ ะฟะพะปัƒั‡ะธั‚ัŒ ะฐั€ั‚ะตั„ะฐะบั‚ั‹ ะฒ ัะฒะพะตะผ build-dirโ€™ะต
    - mkdir ./tests || true
    - ln -s /mnt/tests/${CI_COMMIT_REF_SLUG} ./tests/${CI_COMMIT_REF_SLUG}
  after_script:
# ะฟะพัะปะต ะพะบะพะฝั‡ะฐะฝะธั ั‚ะตัั‚ะพะฒ ัƒะดะฐะปัะตะผ ั€ะตะปะธะท ะฒะผะตัั‚ะต ั Jobโ€™ะพะผ
# (ะธ, ะฒะพะทะผะพะถะฝะพ, ะตะณะพ ะธะฝั„ั€ะฐัั‚ั€ัƒะบั‚ัƒั€ะพะน)
    - type multiwerf && source <(multiwerf use ${WERF_VERSION})
    - werf version
    - type werf && source <(werf ci-env gitlab --tagging-strategy tag-or-branch --verbose)
    - werf dismiss --namespace ${CI_ENVIRONMENT_SLUG} --with-namespace
# ะผั‹ ั€ะฐะทั€ะตัˆะฐะตะผ ะฟะฐะดะตะฝะธั, ะฝะพ ะฒั‹ ะผะพะถะตั‚ะต ัะดะตะปะฐั‚ัŒ ะธะฝะฐั‡ะต
  allow_failure: true
  variables:
    RUN_TESTS: 'yes'
# ะทะฐะดะฐะตะผ ะบะพะฝั‚ะตะบัั‚ ะฒ werf
# (https://werf.io/how_to/gitlab_ci_cd_integration.html#infrastructure)
    WERF_KUBE_CONTEXT: 'admin@stage-cluster'
  tags:
# ะธัะฟะพะปัŒะทัƒะตะผ ั€ะฐะฝะฝะตั€ ั ั‚ะตะณะพะผ `werf-runner`
    - werf-runner
  artifacts:
# ั‚ั€ะตะฑัƒะตั‚ัั ัะพะฑั€ะฐั‚ัŒ ะฐั€ั‚ะตั„ะฐะบั‚ ะดะปั ั‚ะพะณะพ, ั‡ั‚ะพะฑั‹ ะตะณะพ ะผะพะถะฝะพ ะฑั‹ะปะพ ัƒะฒะธะดะตั‚ัŒ
# ะฒ ะฟะฐะนะฟะปะฐะนะฝะต ะธ ัะบะฐั‡ะฐั‚ัŒ โ€” ะฝะฐะฟั€ะธะผะตั€, ะดะปั ะฑะพะปะตะต ะฒะดัƒะผั‡ะธะฒะพะณะพ ะธะทัƒั‡ะตะฝะธั
    paths:
      - ./tests/${CI_COMMIT_REF_SLUG}/*
# ะฐั€ั‚ะตั„ะฐะบั‚ั‹ ัั‚ะฐั€ัˆะต ะฝะตะดะตะปะธ ะฑัƒะดัƒั‚ ัƒะดะฐะปะตะฝั‹
    expire_in: 7 day
# ะฒะฐะถะฝะพ: ัั‚ะธ ัั‚ั€ะพะบะธ ะพั‚ะฒะตั‡ะฐัŽั‚ ะทะฐ ะฟะฐั€ัะธะฝะณ ะพั‚ั‡ะตั‚ะฐ GitLabโ€™ะพะผ
    reports:
      junit: ./tests/${CI_COMMIT_REF_SLUG}/report.xml

# ะดะปั ัƒะฟั€ะพั‰ะตะฝะธั ะทะดะตััŒ ะฟะพะบะฐะทะฐะฝั‹ ะฒัะตะณะพ ะดะฒะต ัั‚ะฐะดะธะธ
# ะฒ ั€ะตะฐะปัŒะฝะพัั‚ะธ ะถะต ัƒ ะฒะฐั ะธั… ะฑัƒะดะตั‚ ะฑะพะปัŒัˆะต โ€” ะบะฐะบ ะผะธะฝะธะผัƒะผ ะธะท-ะทะฐ ะดะตะฟะปะพั
stages:
  - build
  - tests

build:
  stage: build
  script:
# ัะฑะพั€ะบะฐ โ€” ัะฝะพะฒะฐ ะฟะพ ะดะพะบัƒะผะตะฝั‚ะฐั†ะธะธ ะฟะพ werf
# (https://werf.io/how_to/gitlab_ci_cd_integration.html#build-stage)
    - type multiwerf && source <(multiwerf use ${WERF_VERSION})
    - werf version
    - type werf && source <(werf ci-env gitlab --tagging-strategy tag-or-branch --verbose)
    - werf build-and-publish --stages-storage :local
  tags:
    - werf-runner
  except:
    - schedules

run tests:
  <<: *test-base
  environment:
# "ัะฐะผะฐ ัะพะปัŒ" ะธะผะตะฝะพะฒะฐะฝะธั namespaceโ€™ะฐ
# (https://docs.gitlab.com/ce/ci/variables/predefined_variables.html)
    name: tests-${CI_COMMIT_REF_SLUG}
  stage: tests
  except:
    - schedules

ืงื•ื‘ืจื ื˜

ืขื›ืฉื™ื• ื‘ืกืคืจื™ื™ื” .helm/templates ื‘ื•ืื• ื ื™ืฆื•ืจ ืืช YAML ืขื ืื™ื•ื‘ - tests-job.yaml - ืœื”ืคืขื™ืœ ื‘ื“ื™ืงื•ืช ื•ืืช ืžืฉืื‘ื™ Kubernetes ื”ื“ืจื•ืฉื™ื ืœื”ื. ืจืื” ื”ืกื‘ืจื™ื ืœืื—ืจ ื”ืจื™ืฉื•ื:

{{- if eq .Values.global.run_tests "yes" }}
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: tests-script
data:
  tests.sh: |
    echo "======================"
    echo "${APP_NAME} TESTS"
    echo "======================"

    cd /app
    npm run test:ci
    cp report.xml /app/test_results/${CI_COMMIT_REF_SLUG}/

    echo ""
    echo ""
    echo ""

    chown -R 999:999 /app/test_results/${CI_COMMIT_REF_SLUG}
---
apiVersion: batch/v1
kind: Job
metadata:
  name: {{ .Chart.Name }}-test
  annotations:
    "helm.sh/hook": post-install,post-upgrade
    "helm.sh/hook-weight": "2"
    "werf/watch-logs": "true"
spec:
  activeDeadlineSeconds: {{ .Values.global.ci_timeout }}
  backoffLimit: 1
  template:
    metadata:
      name: {{ .Chart.Name }}-test
    spec:
      containers:
      - name: test
        command: ['bash', '-c', '/app/tests.sh']
{{ tuple "application" . | include "werf_container_image" | indent 8 }}
        env:
        - name: env
          value: {{ .Values.global.env }}
        - name: CI_COMMIT_REF_SLUG
          value: {{ .Values.global.commit_ref_slug }}
       - name: APP_NAME
          value: {{ .Chart.Name }}
{{ tuple "application" . | include "werf_container_env" | indent 8 }}
        volumeMounts:
        - mountPath: /app/test_results/
          name: data
        - mountPath: /app/tests.sh
          name: tests-script
          subPath: tests.sh
      tolerations:
      - key: dedicated
        operator: Exists
      - key: node-role.kubernetes.io/master
        operator: Exists
      restartPolicy: OnFailure
      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: {{ .Chart.Name }}-pvc
      - name: tests-script
        configMap:
          name: tests-script
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: {{ .Chart.Name }}-pvc
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 10Mi
  storageClassName: {{ .Chart.Name }}-{{ .Values.global.commit_ref_slug }}
  volumeName: {{ .Values.global.commit_ref_slug }}

---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: {{ .Values.global.commit_ref_slug }}
spec:
  accessModes:
  - ReadWriteOnce
  capacity:
    storage: 10Mi
  local:
    path: /mnt/tests/
  nodeAffinity:
   required:
     nodeSelectorTerms:
     - matchExpressions:
       - key: kubernetes.io/hostname
         operator: In
         values:
         - kube-master
  persistentVolumeReclaimPolicy: Delete
  storageClassName: {{ .Chart.Name }}-{{ .Values.global.commit_ref_slug }}
{{- end }}

ืื™ื–ื” ืกื•ื’ ืฉืœ ืžืฉืื‘ื™ื ื”ืžืชื•ืืจ ื‘ืชืฆื•ืจื” ื–ื•? ื‘ืขืช ื”ืคืจื™ืกื”, ืื ื• ื™ื•ืฆืจื™ื ืžืจื—ื‘ ืฉืžื•ืช ื™ื™ื—ื•ื“ื™ ืขื‘ื•ืจ ื”ืคืจื•ื™ืงื˜ (ื–ื” ืžืฆื•ื™ืŸ ื‘ .gitlab-ci.yaml - tests-${CI_COMMIT_REF_SLUG}) ื•ื’ืœื’ืœ ืื•ืชื• ื”ื—ื•ืฆื”:

  1. ConfigMap ืขื ืชืกืจื™ื˜ ื‘ื“ื™ืงื”;
  2. ืขื‘ื•ื“ื” ืขื ืชื™ืื•ืจ ืฉืœ ื”ืชืจืžื™ืœ ื•ื”ื”ื ื—ื™ื” ืฉืฆื•ื™ื ื” command, ืฉืจืง ืžืจื™ืฅ ืืช ื”ื‘ื“ื™ืงื•ืช;
  3. PV ื•-PVC, ื”ืžืืคืฉืจื™ื ืœืš ืœืื—ืกืŸ ื ืชื•ื ื™ ื‘ื“ื™ืงื”.

ืฉื™ืžื• ืœื‘ ืœืชื ืื™ ื”ืžื‘ื•ื ืขื if ื‘ืชื—ื™ืœืช ื”ืžื ื™ืคืกื˜ - ื‘ื”ืชืื, ื™ืฉ ืœืขื˜ื•ืฃ ืงื‘ืฆื™ YAML ืื—ืจื™ื ืฉืœ ืชืจืฉื™ื Helm ืขื ื”ืืคืœื™ืงืฆื™ื” ะพะฑั€ะฐั‚ะฝัƒัŽ ืœืชื›ื ืŸ ื›ืš ืฉื”ื ืœื ื™ื™ืคืจืกื• ื‘ืžื”ืœืš ื”ื‘ื“ื™ืงื”. ื–ื”:

{{- if ne .Values.global.run_tests "yes" }}
---
ั ะดั€ัƒะณะพะน ัะผะปะธะบ
{{- end }}

ืขื ื–ืืช, ืื ื”ื‘ื“ื™ืงื•ืช ื“ื•ืจืฉื™ื ืชืฉืชื™ืช ื›ืœืฉื”ื™ (ืœื“ื•ื’ืžื”, Redis, RabbitMQ, Mongo, PostgreSQL...) - ื”-YAML ืฉืœื”ื ื™ื›ื•ืœื™ื ืœื”ื™ื•ืช ืœื ืœื›ื‘ื•ืช. ืคืจื•ืก ืื•ืชื ื’ื ื‘ืกื‘ื™ื‘ืช ืžื‘ื—ืŸ... ื”ืชืืžืช ืื•ืชื ื›ืจืื•ืช ืขื™ื ื™ืš, ื›ืžื•ื‘ืŸ.

ื”ืžื’ืข ื”ืกื•ืคื™

ื›ื™ ื”ืจื›ื‘ื” ื•ืคืจื™ืกื” ื‘ืืžืฆืขื•ืช werf ืขื“ื™ื™ืŸ ืขื•ื‘ื“ืช ืจืง ื‘ืฉืจืช ื”-build (ืขื gitlab-runner), ื•ื”ืคื•ื“ ืขื ื”ื‘ื“ื™ืงื•ืช ืžื•ืคืขืœ ืขืœ ื”ืžืืกื˜ืจ, ืชืฆื˜ืจืš ืœื™ืฆื•ืจ ืกืคืจื™ื™ื” /mnt/tests ืขืœ ื”ืžืืกื˜ืจ ื•ืชืŸ ืื•ืชื• ืœืจืฅ, ืœืžืฉืœ, ื“ืจืš NFS. ื“ื•ื’ืžื” ืžืคื•ืจื˜ืช ืขื ื”ืกื‘ืจื™ื ื ื™ืชืŸ ืœืžืฆื•ื ื‘ ืชื™ืขื•ื“ K8s.

ื”ืชื•ืฆืื” ืชื”ื™ื”:

user@kube-master:~$ cat /etc/exports | grep tests
/mnt/tests    IP_gitlab-builder/32(rw,nohide,insecure,no_subtree_check,sync,all_squash,anonuid=999,anongid=998)

user@gitlab-runner:~$ cat /etc/fstab | grep tests
IP_kube-master:/mnt/tests    /mnt/tests   nfs4    _netdev,auto  0       0

ืืฃ ืื—ื“ ืœื ืื•ืกืจ ืœื‘ืฆืข ืฉื™ืชื•ืฃ NFS ื™ืฉื™ืจื•ืช ื‘-gitlab-runner, ื•ืœืื—ืจ ืžื›ืŸ ืœื”ืจื›ื™ื‘ ืื•ืชื• ื‘-pods.

ืฉื™ื ืœื‘

ืืชื” ืื•ืœื™ ืฉื•ืืœ ืœืžื” ืœืกื‘ืš ื”ื›ืœ ืขืœ ื™ื“ื™ ื™ืฆื™ืจืช ื’'ื•ื‘ ืื ืืชื” ื™ื›ื•ืœ ืคืฉื•ื˜ ืœื”ืจื™ืฅ ืกืงืจื™ืคื˜ ืขื ื‘ื“ื™ืงื•ืช ื™ืฉื™ืจื•ืช ืขืœ ืจืฅ ื”ืžืขื˜ืคืช? ื”ืชืฉื•ื‘ื” ื“ื™ ื˜ืจื™ื•ื•ื™ืืœื™ืช...

ื—ืœืง ืžื”ื‘ื“ื™ืงื•ืช ื“ื•ืจืฉื•ืช ื’ื™ืฉื” ืœืชืฉืชื™ืช (MongoDB, RabbitMQ, PostgreSQL ื•ื›ื•') ื›ื“ื™ ืœื•ื•ื“ื ืฉื”ืŸ ืคื•ืขืœื•ืช ื›ื”ืœื›ื”. ืื ื• ื”ื•ืคื›ื™ื ืืช ื”ื‘ื“ื™ืงื•ืช ืœืžืื•ื—ื“ื•ืช - ืขื ื’ื™ืฉื” ื–ื•, ืงืœ ืœื›ืœื•ืœ ื™ืฉื•ื™ื•ืช ื ื•ืกืคื•ืช ื›ืืœื”. ื‘ื ื•ืกืฃ ืœื–ื”, ืื ื—ื ื• ืžืงื‘ืœื™ื ืกื˜ื ื“ืจื˜ื™ ื’ื™ืฉืช ืคืจื™ืกื” (ื’ื ืื ืžืฉืชืžืฉื™ื ื‘-NFS, ื”ืจื›ื‘ื” ื ื•ืกืคืช ืฉืœ ืกืคืจื™ื•ืช).

ืชื•ืฆืื”

ืžื” ื ืจืื” ื›ืฉื ื—ื™ืœ ืืช ื”ืชืฆื•ืจื” ื”ืžื•ื›ื ื”?

ื‘ืงืฉืช ื”ืžื™ื–ื•ื’ ืชืฆื™ื’ ื ืชื•ื ื™ื ืกื˜ื˜ื™ืกื˜ื™ื™ื ืžืกื›ื ืขื‘ื•ืจ ื‘ื“ื™ืงื•ืช ื”ืžื•ืคืขืœื•ืช ื‘ืฆื™ื ื•ืจ ื”ืื—ืจื•ืŸ ืฉืœื”:

JUnit ื‘- GitLab CI ืขื Kubernetes

ื›ืœ ืฉื’ื™ืื” ื ื™ืชืŸ ืœืœื—ื•ืฅ ื›ืืŸ ืœืคืจื˜ื™ื:

JUnit ื‘- GitLab CI ืขื Kubernetes

NB: ื”ืงื•ืจื ื”ืงืฉื•ื‘ ื™ืฉื™ื ืœื‘ ืฉืื ื• ื‘ื•ื“ืงื™ื ืืคืœื™ืงืฆื™ื™ืช NodeJS, ื•ื‘ืฆื™ืœื•ืžื™ ื”ืžืกืš - .NET... ืืœ ืชืชืคืœืื•: ืจืง ืฉื‘ื–ืžืŸ ื”ื›ื ืช ื”ืžืืžืจ ืœื ื ืžืฆืื• ืฉื’ื™ืื•ืช ื‘ื‘ื“ื™ืงืช ื”ืืคืœื™ืงืฆื™ื” ื”ืจืืฉื•ื ื”, ืื‘ืœ ื”ืŸ ื ืžืฆืื• ื‘ืื—ืจ.

ืžืกืงื ื”

ื›ืคื™ ืฉืืชื” ื™ื›ื•ืœ ืœืจืื•ืช, ืฉื•ื ื“ื‘ืจ ืœื ืžืกื•ื‘ืš!

ื‘ืื•ืคืŸ ืขืงืจื•ื ื™, ืื ื›ื‘ืจ ื™ืฉ ืœืš ืืกืคืŸ ืงืœื™ืคื•ืช ื•ื”ื•ื ืขื•ื‘ื“, ืื‘ืœ ืืชื” ืœื ืฆืจื™ืš Kubernetes, ืฆื™ืจื•ืฃ ื‘ื“ื™ืงื•ืช ืืœื™ื• ืชื”ื™ื” ืžืฉื™ืžื” ืคืฉื•ื˜ื” ืืคื™ืœื• ื™ื•ืชืจ ืžืžื” ืฉืžืชื•ืืจ ื›ืืŸ. ื•ื‘ืชื•ืš ืชื™ืขื•ื“ GitLab CI ืชืžืฆืื• ื“ื•ื’ืžืื•ืช ืขื‘ื•ืจ Ruby, Go, Gradle, Maven ื•ืขื•ื“ ื›ืžื”.

ื .ื‘.

ืงืจื ื’ื ื‘ื‘ืœื•ื’ ืฉืœื ื•:

ืžืงื•ืจ: www.habr.com

ื”ื•ืกืคืช ืชื’ื•ื‘ื”