barbitoff programmer`s blog

Здесь я публикую заметки из программерской жизни: грабли, на которые мне случилось наступить, проблемы, для которых было найдено элегантное (или не очень) решение, а также все, с чем мне пришлось столкнуться и чем хотелось бы поделиться =)
PS Если хотите меня поблагодарить - на странице есть 3 места, чтобы это сделать =)

вторник, 28 июля 2020 г.

Tele2 в качестве второй SIM и списание денег за интернет

Проблема

У меня в смартфоне установлены 2 SIM-карты - одна основная для звонков и интернета, и вторая - только для звонков (Tele2). Проблема в том, что несмотря на то, что для интернета выбрана первая SIM, Tele2 все равно списывает деньги за использование интернета, причем в выписке указываются какие-то смешные объемы трафика (не больше 1Кб). На старом смартфоне (Meizu 15) проблемы не было, она появилась только при переходе на Samsung Galaxy M31.

Решение

Сначала я попробовал в настройках для второй сим установить некорркетный APN. Но это не помогло - списания продолжились. А вот что реально помогло - сменить режим сети с LTE/3G/2G на 3G/2G (Подключения => Диспетчер SIM-карт => нажать по нужной SIM => Режим сети).

Antora: открытие ссылки в новой вкладке

Чтобы ссылка (например, на вложение) открывалась в новой вкладке, необходимо добавить атрибут window=_blank:
link:{attachmentsdir}/my-attachment.zip[My attachment, window=_blank]

понедельник, 27 июля 2020 г.

пятница, 24 июля 2020 г.

Ubuntu 18.04 под WSL: как переопределить resolv.conf

Проблема

WSL при генерации /etc/resolv.conf размещает DNS-сервера не в том порядке, в котором они идут в Windows. В моем конкретном случае это приводит к тому, что корпоративные ресурсы из-под Ubuntu резолвятся не в интранет, а в интернет-IP-адреса, что много чего ломает. 
При этом в файле /etc/resolv.conf первая строка следующая:
#This file was automatically generated by WSL. To stop automatic generation of this file, remove this line
Казалось бы, можно удалить эту строку, расставить DNS-сервера в правильном порядке, и наступит счастье. Но нет, файл все равно перегенеривается заново каждый раз, когда я запускаю bash в Ubuntu.

Решение

Удалить /etc/resolv.conf, затем создать его заново и прописать туда DNS-сервера в нужном порядке. Спасибо https://github.com/microsoft/WSL/issues/1908#issuecomment-315830161.

среда, 3 июня 2020 г.

Elasticsearch & Amazon EFS

Проблема

Elasticsearch, развернутый в Amazon EKS и использующий EFS для хранения данных, не работает, в логах ошибка "java.io.IOException: Disk quota exceeded".

Причина

Amazon EFS имеет ограничение на число пар "процесс-заблокированный файл", равное 256. Если зайти в контейнер Эластика и выполнить lslocks, то получаем как раз эти самые 256 блокировок.

Решение

Отказ от использования EFS для хранения данных Эластиком.

понедельник, 13 апреля 2020 г.

Grep: поиск строки, начинающейся с дефиса

Задача

Выполнить поиск подстроки, начинающейся с "-", например, "->". При попытке сделать grep "->" получаем ошибку grep: invalid option -- '>', т.к. grep трактует искомую строку как опцию.

Решение

grep -- "->"

понедельник, 6 апреля 2020 г.

Gradle 6 Jacoco plugin: как исключить пакеты и классы из отчетов и верификации

В конфигурацию тасков jacocoTestReport и/или jacocoTestCoverageVerification добавляем:
    afterEvaluate {
        classDirectories.setFrom(files(classDirectories.files.collect {
            fileTree(dir: it, exclude: [
                    '**/poc/**',
                    '**/model/**',
                    '**/*Config.class',
                    '**/*Application.class'

            ])
        })
        )
    }
Где, собственно, и указываем маски на папки и файлы для исключения. 

среда, 18 марта 2020 г.

Spring boot 2.2.5 и Tinkoff openapi-java-sdk-java8 0.4.1: конфликт версий okhttp

Есть проект, собираемый Gradle. При попытке использовать Tinkoff openapi-java-sdk-java8 версии 0.4.1 вместе со Spring boot 2.2.5 возникает ошибка вида:
An attempt was made to call a method that does not exist. The attempt was made from the following location:

     ru.tinkoff.invest.openapi.okhttp.SandboxContextImpl.performRegistration(SandboxContextImpl.java:45)

The following method did not exist:

     okhttp3.RequestBody.create([B)Lokhttp3/RequestBody;
Если сделать gradle dependencies, то видна и причина данной ошибки:
com.squareup.okhttp3:okhttp:4.3.1 -> 3.14.7
Видно, что версия okhttp, требуемая для openapi-java-sdk, даунгэйдится до 3.14.7. Причина - в https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-dependencies/2.2.5.RELEASE, где определяется версия 3.14.7. 
Выход из ситуации - добавление в build.gradle:
ext['okhttp3.version'] = '4.3.1'
В целом про переопределение версий зависимостей, диктуемых spring boot'ом, написано тут: https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#howto-customize-dependency-versions.

четверг, 5 марта 2020 г.

minikube для Windows: как дать больше памяти?

При старте minikube есть возможность установить опцию --memory с большим/меньшим размером памяти, чем дефолтные 3Гб. Однако, чтобы эта опция действительно применялась, придется пересоздать кластер, т.е. сделать:
minikube stop
minikube delete
minikube start --memory=4096m
Тогда получим в логе заветные:
Creating hyperv VM (CPUs=2, Memory=4096MB, Disk=20000MB)
, которых нет при обычном stop/start.

Необходимость удаления вызвана тем, что объем памяти - это параметр HyperV VM, которая создается единожды и при последующих запусках лишь переиспользуется. Возможно, есть возможность поменять объем памяти HyperV VM без ее удаления, но по крайней мере средствами minikube cli этого не сделать.

пятница, 21 февраля 2020 г.

echo и многострочные переменные окружения

Задача

Есть переменная окружения, содержащая текст с несколькими строками. Нужно сделать echo этой переменной так, чтобы не потерялись переводы строк.

Решение

Если делать просто echo $var, переносы строки теряются. А вот если заключить переменную в кавычки:
echo "$var"
,то строки выводятся корректно.

среда, 19 февраля 2020 г.

GitLab CI/CD и Gradle: корректное кэширование и передача результатов запуска между джобами

Кейс

Есть проект, собираемый при помощи Gradle, и CI/CD-пайплайн в GitLab, выполняющий некоторые Gradle-таски на разных stage. Необходимо:

  1. Реализовать кэширование зависимостей, чтобы не выкачивать их заново на каждом stage, а также при каждом новом запуске pipeline
  2. Реализовать кэширование результатов сборки, чтобы последующий stage выполнял не всю сборку "с нуля", а использовал результаты предыдущих stage'й
Решение

Во-первых, настраиваем GRADLE_USER_HOME, чтобы он размещался внутри сборочной директории, и настраиваем кэширование для папки с зависимостями внутри GRADLE_USER_HOME в соответствии с https://docs.gradle.org/6.1.1/userguide/dependency_resolution.html#sec:dependency_cache:
before_script:
  - export GRADLE_USER_HOME=`pwd`/.gradle
myjob:
  ...
  cache:
    key: gradle_modules_cache
    paths:
      - .gradle/caches/modules-2
    policy: pull-push
Т.к. это кэш, он будет разделяться не только джобами внутри пайплайна, но и между разными пайплайнами.

Во-вторых, в settings.gradle настраиваем кастомную директорию для билд-кэша (см. https://docs.gradle.org/current/userguide/build_cache.html):
buildCache {
    local {
        directory = new File(rootDir, 'build-cache')
    }
}
и для промежуточных джобов настраиваем публикацию этой директории в качестве артефакта:
  artifacts:
    paths:
      - build-cache/
    expire_in: 1 hour
В последующих джобах прописываем dependencies на предыдущие джобы, чтобы они подхватывали build-cache/ оттуда. Таким образом, build cache будет передаваться между джобами, но не будет передаваться между пайплайнами, что как раз корректно.

Ну и последнее - при вызове gradle используем --build-cache, чтобы собственно включить использования билд-кэша.

Вуаля, в логах сборки видим, что результаты предыдущих джобов подтягиваются из кэша:


вторник, 11 февраля 2020 г.

Windows 10 и несколько мониторов: отображение на таскбаре каждого монитора только находящихся на нем окон

Задача

Есть машина с Windows 10 и несколькими мониторами. Хочется, чтобы на каждом мониторе был таскбар, и на каждом таскбаре отображались иконки только тех окон, которые действительно находятся на этом мониторе.

Решение

Долгие годы для этих целей использовал замечательную программу DisplayFusion, честно купленную на Steam. Но только сегодня выяснил, что подобное поведение можно настроить в Windows "из коробки": идем в Settings -> Taskbat settings и там устанавливаем:

вторник, 4 февраля 2020 г.

Docker Desktop: рассинхронизация времени в контейнерах и у хоста

Проблема

Неожиданно обнаружилось, что все контейнеры, работающие в Docker Desktop, видят время, отстающее от времени хоста аж на 6 с лишним дней.

Решение

Корень проблемы не ясен, но рестарт Docker Desktop помог.

пятница, 31 января 2020 г.

bash: ожидание, пока не будет открыт порт

Задача

В bash-скрипте нужно подождать, пока не станет доступен определенный tcp-порт, но ждать нужно не более определенного времени.

Решение
for ((i=0;  i< 10; i++)) do if(nc -q0 localhost 8080 < /dev/null > /dev/null 2>&1) then break; fi; echo "sleeping"; sleep 2; done

Раз в 2 секунды проверяем открытость localhost:8080, но не более 10 проверок. Такой вариант работает в Ubuntu 18.04, а вот с CentOS пришлось убрать -q0 опцию, т.к. она там не поддерживается.

вторник, 28 января 2020 г.

Grafana в Kubernetes: автоматизация развертывания дашбордов

Задача 

Есть JSON/YAML-описания дашбордов для графаны, выкладываемые разработчиками в gitlab. Необходимо в CI/CD-пайплайне автоматизировать развертывание этих дашборд на окружении при условии, что графана установлена внутри Kubernetes.

Решение

На тему автоматизации развертывания дашбордов в случае, когда Grafana работает в Kubernetes, есть отличная статья: https://medium.com/@chris_linguine/how-to-monitor-your-kubernetes-cluster-with-prometheus-and-grafana-2d5704187fc8. В целом, сценарий выглядит следующим образом:
  1. Описания дашбордов пакуем внутрь ConfigMap-объектов Kubernetes и разворачиваем эти объекты в кластере (делаем kubectl apply -f ... внутри нашего CI/CD pipeline)
  2. Графану конфигурируем (путем установки необходимых значений в helm) таким образом, чтобы она разворачивалась с сайдкаром, который будет мониторить наличие ConfigMap-объектов с описаниям дашбордов и "скармливать" эти описания работающей графане.

понедельник, 20 января 2020 г.

bash: echo без перевода строки в конце

Задача

Выполнить echo таким образом, чтобы в конце строки не был добавлен символ line feed. Это бывает нужно, к примеру, если результат echo отправляется куда-то дальше, скажем, в base64, и важно, чтобы в принимающую команду вывод echo пришел в неизменном виде, без лишних переводов строк в конце.

Решение

echo -e "somestring\c"
т.е. включаем обработку управляющих символов, и используем \c, чтобы оставить каретку на текущей строке (см. https://www.opennet.ru/man.shtml?topic=echo&category=1).

пятница, 17 января 2020 г.

GitLab CI cache: WARNING: /home/usr1/.m2/repository/: no matching files

Проблема

Пытаюсь закэшировать репозиторий мавена для GitLab CI/CD пайплайна. Настраиваю:
  cache:
    key: m2_repo
    paths:
      - /home/usr1/.m2/repository/
    policy: pull-push
Однако, при сборке получаю:
WARNING: /root/.m2/repository/: no matching files
Причина 

Кэширование работает только для директорий внутри сборочной директории. Поэтому необходимо сделать так, чтобы репозиторий мавен размещался внутри неё (через -Dmaven.repo.local).

четверг, 16 января 2020 г.

GitLab и Kubernetes: не видны переменные $KUBE_URL и $KUBE_TOKEN

Проблема

Для группы проектов в GitLab настроена интеграция с единственным Kubernetes-кластером. Однако, внутри job-ов CI/CD переменные $KUBE_URL и $KUBE_TOKEN имеют пустые значения, из-за чего невозможно сконфигурировать kubectl для работы с кластером Kubernetes.

Решение

Для job'а, в котором нужны переменные $KUBE_URL и $KUBE_TOKEN, должен быть определен environment (любой, т.к. при единственном привязанном Kubernetes-кластере он применяется для любого environment'а). Например:
deploy:
  stage: deploy
  environment:
    name: production # needed for KUBE_* variables to be materialized
  script:
    - kubectl config set-cluster mycluster --server="$KUBE_URL" --certificate-authority="$KUBE_CA_PEM_FILE"
    - kubectl config set-credentials gitlab-admin --token="$KUBE_TOKEN"
    - kubectl config set-context default --cluster=mycluster --user=gitlab-admin
    - kubectl config use-context default
    - echo "Deployment started"
    - kubectl apply -f my-deployment.yaml
    - echo "Deployment succeeded"
  only:
    - master