FIX ERROR — RDS: Error creating DB Parameter Group: InvalidParameterValue: ParameterGroupFamily

При создании RDS указав не верное значение параметра "ParameterGroupFamily" может возникнуть похожая ошибка:

Error creating DB Parameter Group: InvalidParameterValue: ParameterGroupFamily default.mariadb10.2 is not a valid parameter group family

 

Чтобы посмотреть список всех возможных значений параметра "ParameterGroupFamily" можно использовать следующую команду:

aws rds describe-db-engine-versions --query "DBEngineVersions[].DBParameterGroupFamily"

Docker Compose — Задаем лимит размера логов

По умолчанию Docker Compose не задает никаких лимитов на размер логов. Для примера зададим лимит в 10 Мб и максимальное количество файлов для ротации — 10.

version: "3.8"
services:
  some-service:
    image: some-service
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "10"

Nginx — Regular Expression Tester

 

Для быстрого тестирования регулярных выражений Nginx'а, можно воспользоваться готовым докер образом. Для этого нужно клонировать репозиторий NGINX-Demos:

git clone https://github.com/nginxinc/NGINX-Demos

 

Переходим в директорию "nginx-regex-tester":

cd NGINX-Demos/nginx-regex-tester/

 

И запускаем контейнер с помощью "docker-compose":

docker-compose up -d

 

И открываем следующую страницу:

http://localhost/regextester.php

 

AWS — EKS Fargate — Fluentd CloudWatch

На момент написания статьи EKS Fargate не поддерживал драйверлог для записи в CloudWatch. Единственный вариант — использовать Sidecar

Создадим ConfigMap, в котором укажем имя EKS кластера, регион и namespace:

kubectl create configmap cluster-info \
--from-literal=cluster.name=YOUR_EKS_CLUSTER_NAME \
--from-literal=logs.region=YOUR_EKS_CLUSTER_REGION -n KUBERNETES_NAMESPACE

 

Далее создадим сервис аккаунт и ConfigMap с файлом конфигурации для Fluentd. Для этого скопируем текст ниже и сохраним его как файл "fluentd.yaml"

apiVersion: v1
kind: ServiceAccount
metadata:
  name: fluentd
  namespace: {{NAMESPACE}}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: fluentd-role
rules:
  - apiGroups: [""]
    resources:
      - namespaces
      - pods
      - pods/logs
    verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: fluentd-role-binding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: fluentd-role
subjects:
  - kind: ServiceAccount
    name: fluentd
    namespace: {{NAMESPACE}}
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: fluentd-config
  namespace: {{NAMESPACE}}
  labels:
    k8s-app: fluentd-cloudwatch
data:
  fluent.conf: |
    @include containers.conf

    <match fluent.**>
      @type null
    </match>
  containers.conf: |
    <source>
      @type tail
      @id in_tail_container_logs
      @label @containers
      path /var/log/application.log
      pos_file /var/log/fluentd-containers.log.pos
      tag *
      read_from_head true
      <parse>
        @type none
        time_format %Y-%m-%dT%H:%M:%S.%NZ
      </parse>
    </source>

    <label @containers>
      <filter **>
        @type kubernetes_metadata
        @id filter_kube_metadata
      </filter>

      <filter **>
        @type record_transformer
        @id filter_containers_stream_transformer
        <record>
          stream_name "#{ENV.fetch('HOSTNAME')}"
        </record>
      </filter>

      <filter **>
        @type concat
        key log
        multiline_start_regexp /^\S/
        separator ""
        flush_interval 5
        timeout_label @NORMAL
      </filter>

      <match **>
        @type relabel
        @label @NORMAL
      </match>
    </label>

    <label @NORMAL>
      <match **>
        @type cloudwatch_logs
        @id out_cloudwatch_logs_containers
        region "#{ENV.fetch('REGION')}"
        log_group_name "/aws/containerinsights/#{ENV.fetch('CLUSTER_NAME')}/application"
        log_stream_name_key stream_name
        remove_log_stream_name_key true
        auto_create_stream true
        <buffer>
          flush_interval 5
          chunk_limit_size 2m
          queued_chunks_limit_size 32
          retry_forever true
        </buffer>
      </match>
    </label>

 

И применим его:

curl fluentd.yaml | sed "s/{{NAMESPACE}}/default/" | kubectl apply -f -

 

Где "default" имя нужного неймспейса

 

Пример деплоймента с сайдкаром:

deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: testapp
  name: testapp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: testapp
  strategy: {}
  template:
    metadata:
      labels:
        app: testapp
    spec:
      serviceAccountName: fluentd
      terminationGracePeriodSeconds: 30
      initContainers:
        - name: copy-fluentd-config
          image: busybox
          command: ['sh', '-c', 'cp /config-volume/..data/* /fluentd/etc']
          volumeMounts:
            - name: config-volume
              mountPath: /config-volume
            - name: fluentdconf
              mountPath: /fluentd/etc
      containers:
      - image: alpine:3.10
        name: alpine
        command: ["/bin/sh"]
        args: ["-c", "while true; do echo hello 2>&1 | tee -a /var/log/application.log; sleep 10;done"]
        volumeMounts:
        - name: fluentdconf
          mountPath: /fluentd/etc
        - name: varlog
          mountPath: /var/log
      - image: fluent/fluentd-kubernetes-daemonset:v1.7.3-debian-cloudwatch-1.0
        name: fluentd-cloudwatch
        env:
          - name: REGION
            valueFrom:
              configMapKeyRef:
                name: cluster-info
                key: logs.region
          - name: CLUSTER_NAME
            valueFrom:
              configMapKeyRef:
                name: cluster-info
                key: cluster.name
          - name: AWS_ACCESS_KEY_ID
            value: "XXXXXXXXXXXXXXX"
          - name: "AWS_SECRET_ACCESS_KEY"
            value: "YYYYYYYYYYYYYYY"
        resources:
          limits:
            memory: 400Mi
          requests:
            cpu: 100m
            memory: 200Mi
        volumeMounts:
          - name: config-volume
            mountPath: /config-volume
          - name: fluentdconf
            mountPath: /fluentd/etc
          - name: varlog
            mountPath: /var/log
      volumes:
        - name: config-volume
          configMap:
            name: fluentd-config
        - name: fluentdconf
          emptyDir: {}
        - name: varlog
          emptyDir: {}

 

В данном деплойменте в переменных заданы "AWS_ACCESS_KEY_ID" и "AWS_SECRET_ACCESS_KEY", так как в данный момент существуют эндпоинты для IAM ролей только для сервисов: EC2, ECS Fargate и Lambda. Чтобы избежать этого, вы можете использовать OpenID Connect провайдер для EKS.

Kubernetes — Одна роль для нескольких пространств имен

 

 

Цель:

Есть 2 пространства имен, это "kube-system" и "default". Нужно запускать крон задачу в пространстве "kube-system", которая будет очищать выполненные джобы и поды в пространстве "default". Для этого создадим сервис аккаунт в пространстве "kube-system", роль с необходимыми правами в пространстве "default", и для созданного аккаунта привязываем созданную роль.

cross-namespace-role.yaml

apiVersion: v1
kind: ServiceAccount
metadata:
  name: jobs-cleanup
  namespace: kube-system
automountServiceAccountToken: false
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:
  name: jobs-cleanup
  namespace: default
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "watch", "list", "delete"]
- apiGroups: ["batch", "extensions"]
  resources: ["jobs"]
  verbs: ["get", "list", "watch", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: jobs-cleanup
  namespace: default
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: jobs-cleanup
subjects:
- kind: ServiceAccount
  name: jobs-cleanup
  namespace: kube-system

FIX ERROR — EKS: cloudwatch — 0/1 nodes are available: 1 Insufficient pods.

После установки CloudWatch Agent'а в EKS кластер, его поды повисают в состоянии "Pending"

 

Смотрим дескрайб пода

 

Решение:

Решение было найдено тут. В данном EKS кластере для системных подов выделена отдельная NodeGroup'а с типом истансов: t3.micro и для запуска еще и CloudWatch Agent'a просто не хватает капасити.

 

После изменения типа инстанса в большую сторону — t3.small, все поды перешли в статус "Running"

EKS имеет лимиты на количество подов на каждой ноде, данный лимит можно посчитать используя следующую формулу:

N * (M-1) + 2

Где, N — количество Elastic Network Interfaces (ENI) для данного типа инстанса, M — количество IP адрессов для одного ENI

Значения N и M для конкретного инстанса можно взять тут.

FIX ERROR — fdisk: DOS partition table format cannot be used on drives for volumes larger than 2199023255040 bytes for 512-byte sectors

При попытке создать раздел утилитой "fdisk" размером 2 и более терабайта появляется следующее сообщение:

The size of this disk is 2 TiB (2199023255552 bytes). DOS partition table format cannot be used on drives for volumes larger than 2199023255040 bytes for 512-byte sectors. Use GUID partition table format (GPT).

 

Решение:

Поставить на новый диск лейбл "gpt"

parted /dev/nvme2n1

 

Где "nvme2n1" — ваш диск

 

Ставим лейбл:

mklabel gpt

 

Проверяем:

print

 

 

После чего возвращаемся в fdisk и создаем нужный раздел. Раздел так же можно создать и в утилите "parted"

 

Создадим файловую систему, для этого скопируем полный путь созданного раздела

 

И создаем файловую систему, в данном примере это ext4:

mkfs.ext4 /dev/nvme2n1p1

 

Jenkins — Active Choice: GitHub — Commit

 

Для параметризованной сборки с выбором тега образа, понадобится плагин Active Choices

Переходим в настройки Jenkins

 

Раздел "Управление плагинами"

 

Переходим к вкладке "Доступные" и в поиске указываем "Active Choices"

Устанавливаем его. Так же необходим плагин Amazon Web Services SDK

Создаем "New Item" — "Pipeline", указываем, что это будет параметризованной сборка, и добавляем параметр "Active Choices Reactive Parameter"

 

Указываем, что это "Groovy Script" и вставляем туда следующее:

import jenkins.model.*
import groovy.json.JsonSlurper

credentialsId = 'artem-github'
gitUri = '[email protected]:artem-gatchenko/ansible-openvpn-centos-7.git'

def creds = com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials(
  com.cloudbees.plugins.credentials.common.StandardUsernameCredentials.class, Jenkins.instance, null, null ).find{
    it.id == credentialsId}

def slurper = new JsonSlurper()

def account = gitUri.split(":")[-1].split("/")[0]
def repo = gitUri.split(":")[-1].split("/")[-1].split("\\.")[0]

def addr = "https://api.github.com/repos/${account}/${repo}/commits"
def authString = "${creds.username}:${creds.password}".getBytes().encodeBase64().toString()
def conn = addr.toURL().openConnection()
conn.setRequestProperty( "Authorization", "Basic ${authString}" )
def response_json = "${conn.content.text}"
def parsed = slurper.parseText(response_json)
def commit = []
commit.add("Latest")
for (int i = 0; i < parsed.size(); i++) {
    commit.add(parsed.get(i).sha)
}
return commit

 

Где значение переменных, "credentialsId" — Jenkins Credentials ID с токеном к GitHub'у;

"gitUri" — полный путь к нужному репозиторию;

 

 

Тоже самое, но уже через Pipeline

Pipeline:

properties([
  parameters([
    [$class: 'StringParameterDefinition',
      defaultValue: '[email protected]:artem-gatchenko/ansible-openvpn-centos-7.git',
      description: 'Git repository URI',
      name: 'gitUri',
      trim: true
    ],
    [$class: 'CascadeChoiceParameter', 
      choiceType: 'PT_SINGLE_SELECT', 
      description: 'Select Image',
      filterLength: 1,
      filterable: false,
      referencedParameters: 'GIT_URI',
      name: 'GIT_COMMIT_ID', 
      script: [
        $class: 'GroovyScript', 
        script: [
          classpath: [], 
          sandbox: false, 
          script: 
            '''
            import jenkins.model.*
            import groovy.json.JsonSlurper

            credentialsId = 'artem-github'

            def creds = com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials(
              com.cloudbees.plugins.credentials.common.StandardUsernameCredentials.class, Jenkins.instance, null, null ).find{
                it.id == credentialsId}

            def slurper = new JsonSlurper()

            def account = gitUri.split(":")[-1].split("/")[0]
            def repo = gitUri.split(":")[-1].split("/")[-1].split("\\\\.")[0]

            def addr = "https://api.github.com/repos/${account}/${repo}/commits"
            def authString = "${creds.username}:${creds.password}".getBytes().encodeBase64().toString()
            def conn = addr.toURL().openConnection()
            conn.setRequestProperty( "Authorization", "Basic ${authString}" )
            def response_json = "${conn.content.text}"
            def parsed = slurper.parseText(response_json)
            def commit = []
            commit.add("Latest")
            for (int i = 0; i < parsed.size(); i++) {
                commit.add(parsed.get(i).sha)
            }
            return commit
            '''
        ]
      ]
    ]
  ])
])

AWS Cli — Lambda: Обновить значение одной переменной

Ключ "—environment" утилиты AWS Cli заменяет все переменные, на те которые вы укажете в качестве аргумента. Чтобы изменить значение только одной переменной не стирая другие, или не перечисляя их все, можно воспользоваться следующим BASH скриптом:

 

aws_lambda_update_env.sh:

#!/usr/bin/env bash

# VARIABLES
LAMBDA='ARTEM-SERVICES'
REGION='us-east-1'
VARIABLE='ECR_TAG'
NEW_VALUE='1.0.1'

CURRENTVARIABLES="$(aws lambda get-function-configuration --region $REGION --function-name $LAMBDA | jq '.Environment.Variables')"

NEWVARIABLES=$(echo $CURRENTVARIABLES | jq --arg VARIABLE $VARIABLE --arg NEW_VALUE $NEW_VALUE ".$VARIABLE |= \"$NEW_VALUE\"")

COMMAND="aws lambda update-function-configuration --region $REGION --function-name $LAMBDA --environment '{\"Variables\":$NEWVARIABLES}'"

eval $COMMAND

 

Для работы этого скрипта нужна утилита jq

 

Скрипт вычитывает все текущие переменные, подменяет значение переменной "ECR_TAG" на значение "1.0.1" и обновляет все переменные с измененным значением переменной "ECR_TAG".

FIX ERROR — Jenkins: dial unix /var/run/docker.sock: connect: permission denied

После того, как установили Docker на Jenkins сервер и запустили его, при попытке выполнить сборку получаем следующую ошибку:

dial unix /var/run/docker.sock: connect: permission denied

 

Решение:

Добавляем пользователя "jenkins" в группу "docker":

sudo usermod -aG docker jenkins

 

После этого пользователь "jenkins" сможет выполнять работу с Docker'ом, если подключиться по SSH, но при Jenkins сборке будет по прежнему та же ошибка, чтобы от нее избавится, необходимо перезапустить сервис Jenkins'а:

sudo systemctl restart jenkins