MkDocsのテンプレート

MkDocsを使ってプライベートでも仕事でもドキュメントをまとめることが増えてきました.
リアルタイム編集には向きませんが、やはり設計ドキュメントやオンボーディング資料には向いてる気がします.

そこで自分向けにMkDocsをリポジトリに導入するためのテンプレートを作って、リポジトリにコミットしておきました.

テンプレート

mkdocs-prj-templatesというリポジトリですが、このリポジトリユースケースに応じてテンプレートをディレクトリで分けて管理しています.
現時点では、2種類のテンプレートを作成しています.

simple

simpleは、ドキュメントだけ配置するリポジトリ向けのテンプレートです.

simple
├── Dockerfile
├── docker-compose.yaml
├── docs
│   └── index.md
└── mkdocs.yml

ドキュメントだけでなくても、docker-compose.yamlやDockerfileをリポジトリルートに設置しても気にならないなら、このテンプレートでも良さそうです.

in-docs

in-docsは、既にdocker-compose.yamlやDockerfileがリポジトリにあり、リポジトリルートを散らかしたくない時のテンプレートです.

in-docs
├── bin
│   └── docs-compose
├── docs
│   ├── Dockerfile
│   ├── docker-compose.yaml
│   └── index.md
└── mkdocs.yml

構造を見ると分かるように、Dockerfileとdocker-compose.yamldocsディレクトリに存在しています.
この状態だとリポジトリルートでdocker-compose upをすることができないため、ラッパースクリプトとしてbin/docs-composeを用意しています.
ドキュメント関連の操作はdocs-composeを使えば良いため、各々が独自のコマンドを実行しなくて良いですし、コマンド履歴も比較的キレイになります.

mkdocs.ymlもdocsに入れられる気がしなくもないんですが、今回は諦めました.

aptでnpmをインストールしたらlibmysqlclient-devが削除される

業務でubuntu:18.04イメージをベースとしたコンテナを使っていた時に、困った現象に出会いました.
それはlibmysqlclient-devをインストール後、nodejsnpmをaptでインストールするとlibmysqlclient-devが削除されるという現象です.

結論としては、

  • libmysqlclient-devlibssl-devに依存しているが、npmlibssl1.0-devに依存している
  • npmインストールする際にlibssl-devlibssl1.0-devが競合し、libssl-devと依存しているlibmysqlclient-devを削除する
  • Ubuntu公式リポジトリではなくnodesource/distributionsでaptリポジトリからNodeJSをインストールすれば良い

といった現象でした.

今のいままで問題なく動作していたんですが、昨日から動かなくなっていたので回避策があって良かったです.

挙動確認

挙動確認した時のコマンドを貼っておきます. ubuntu:18.04コンテナ上でaptの操作するだけで確認できます.

# Host
docker run --rm -it ubuntu:18.04 bash

# Ubuntu guest
apt update
apt install -y libmysqlclient-dev
apt install -s --no-remove nodejs npm # ERROR!

最後のapt install -s --no-remove nodejs npmのところのオプションについて軽く説明をしておきます.

-sオプションは、インストールやパッケージ削除などは行わないモードで、いわゆるDryRunです.
--no-removeオプションは、インストール時に他のパッケージが削除される場合にエラー終了させるためのオプションです.

コンテナイメージのビルドでaptを使う場合、--no-removeをつけておいた方が意図しないパッケージの削除が起こらないので、つけておいた方が良さそうです.

MacでネットワークのDNSサーバを変更する

新しいMacのセットアップや、ネットワークの追加した時にDNSサーバを変更する時があります.
その時のセットアップを楽にするためにスクリプトを書きました.

Gistにアップロードしてあるので、具体的なスクリプトをそちらを参照してください.

コマンド各種

dns-manager.sh [verbs] [optins] を意識して書いています.

Wi-Fiネットワークに設定されているDNSサーバを表示する

dns-manager.sh get -s Wi-Fi

Wi-FiネットワークのDNSサーバにCloudflareのPublic DNSを設定する

dns-manager.sh set -s Wi-Fi -d cloudflare

Wi-FiネットワークのDNSサーバにGoogleのPublic DNSを設定する

dns-manager.sh set -s Wi-Fi -d google

Wi-FiネットワークのDNSサーバに指定したIPアドレスを設定する

dns-manager.sh set -s Wi-Fi -d 192.168.1.1 192.168.1.2

Wi-FiネットワークのDNSサーバをクリアする

dns-manager.sh set -s Wi-Fi

conftestを使ってDeploymentにrequests,limitsが指定されているかをチェックする

最近は、プライベートでも仕事でもKubernetesマニフェストを書いていることが多くなってきました.
今回は「Deploymentのコンテナにrequests,limitsが指定されていること」をconftestというツールを使ってチェックする際のポリシーをメモしておきます.

# requestsが指定されていること
deny[msg] {
    input.kind = "Deployment"
    c := input.spec.template.spec.containers[_]
    not c.resources.requests

    msg = sprintf("%sコンテナにrequestsを指定してください", [c.name])
}

# limitsが指定されていること
deny[msg] {
    input.kind = "Deployment"
    c := input.spec.template.spec.containers[_]
    not c.resources.limits

    msg = sprintf("%sコンテナにlimitsを指定してください", [c.name])
}

OPAのIterationを参考に書いています.
OPAのIterationのArrayのサンプルでは、

# iterate over indices i
arr[i]

# iterate over values
val := arr[_]

# iterate over index/value pairs
val := arr[i]

と書かれており、今秋のようにDeployment内のコンテナにrequests,limitsが指定されていることを確認したければ、Iterationをポリシー内で使用してチェックすれば良いわけです.

おまけ

ちょっとしたおまけですが、どのコンテナがポリシー違反なのかを分かりやすくするためにsprintfを使ってコンテナ名を出力しています.
こういった細かい事でも出力するのと、しないのとで実際に利用するときのデバッグのしやすさが変わりますからね.

自宅k8s環境向けにownCloudのマニフェストを書く

週末に自宅で動かしているownCloudを、Kubernetesに乗せるためにマニフェストを定義していた.
私用用途なのでPV使うまでもないかなとhostPathを使ったり、ラベルの指定もまばらで規則性がないので気が向いた時に直しておきたい.

マニフェスト

ira-apps/owncloudで定義しているマニフェストkustomize buildの出力結果は以下のような形です.

Ingress

ファイルアップロードがされるWebアプリなので、とりあえずclient_max_body_sizeを20GBに指定しています.

---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/proxy-body-size: 20G
  labels:
    app.kubernetes.io/component: owncloud-server
    app.kubernetes.io/instance: owncloud
    app.kubernetes.io/managed-by: argocd
    app.kubernetes.io/name: owncloud-server
    app.kubernetes.io/part-of: owncloud
  name: owncloud-server
  namespace: owncloud
spec:
  rules:
  - host: owncloud.local
    http:
      paths:
      - backend:
          serviceName: owncloud-server
          servicePort: http

Service

WebアプリとDBのDeploymentを分けている都合上、Serviceもそれぞれ定義しています.

apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/component: server
    app.kubernetes.io/instance: owncloud
    app.kubernetes.io/managed-by: argocd
    app.kubernetes.io/name: owncloud-server
    app.kubernetes.io/part-of: owncloud
  name: owncloud-server
  namespace: owncloud
spec:
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: http
  selector:
    app.kubernetes.io/instance: owncloud
    app.kubernetes.io/managed-by: argocd
    app.kubernetes.io/name: owncloud-server
    app.kubernetes.io/part-of: owncloud
  type: ClusterIP

---
apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/component: db
    app.kubernetes.io/instance: owncloud
    app.kubernetes.io/managed-by: argocd
    app.kubernetes.io/name: owncloud-db
    app.kubernetes.io/part-of: owncloud
  name: owncloud-db
  namespace: owncloud
spec:
  ports:
  - name: mysql
    port: 3306
    protocol: TCP
    targetPort: mysql
  selector:
    app.kubernetes.io/instance: owncloud
    app.kubernetes.io/managed-by: argocd
    app.kubernetes.io/name: owncloud-db
    app.kubernetes.io/part-of: owncloud
  type: ClusterIP

Secret

今回は載せませんが、SealedSecretを使って定義してます.
導入は多少面倒かもしれませんが、クラウドプロパイダを利用しない場合は手軽です.

Deployment

helm/chartsにあったownCloudのマニフェストを参考にしています.
PVCを利用してソフトウェアRAIDを組んでいるディスクに書き込みたかったんですが、サクッとKubernetsに移行しておきたかったので、hostPathで動かしています.

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app.kubernetes.io/component: server
    app.kubernetes.io/instance: owncloud
    app.kubernetes.io/managed-by: argocd
    app.kubernetes.io/name: owncloud-server
    app.kubernetes.io/part-of: owncloud
  name: server
  namespace: owncloud
spec:
  selector:
    matchLabels:
      app.kubernetes.io/instance: owncloud
      app.kubernetes.io/managed-by: argocd
      app.kubernetes.io/name: owncloud-server
      app.kubernetes.io/part-of: owncloud
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app.kubernetes.io/instance: owncloud
        app.kubernetes.io/managed-by: argocd
        app.kubernetes.io/name: owncloud-server
        app.kubernetes.io/part-of: owncloud
    spec:
      containers:
      - env:
        - name: OWNCLOUD_DOMAIN
          value: localhost
        - name: OWNCLOUD_DB_TYPE
          value: mysql
        - name: OWNCLOUD_MYSQL_UTF8MB4
          value: "true"
        - name: OWNCLOUD_DB_HOST
          value: owncloud-db
        - name: OWNCLOUD_DB_NAME
          value: owncloud
        - name: OWNCLOUD_DB_USERNAME
          value: owncloud
        - name: OWNCLOUD_DB_PASSWORD
          valueFrom:
            secretKeyRef:
              key: MYSQL_PASSWORD
              name: owncloud-db
        image: owncloud/server:10.3.2
        imagePullPolicy: Always
        livenessProbe:
          exec:
            command:
            - /usr/bin/healthcheck
          initialDelaySeconds: 5
          periodSeconds: 30
          timeoutSeconds: 10
        name: owncloud-server
        ports:
        - containerPort: 8080
          name: http
        readinessProbe:
          exec:
            command:
            - /usr/bin/healthcheck
          initialDelaySeconds: 120
          periodSeconds: 30
          timeoutSeconds: 10
        volumeMounts:
        - mountPath: /mnt/data
          name: owncloud-server
      volumes:
      - hostPath:
          path: /geminos/kubernetes/owncloud/server
        name: owncloud-server

---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app.kubernetes.io/component: db
    app.kubernetes.io/instance: owncloud
    app.kubernetes.io/managed-by: argocd
    app.kubernetes.io/name: owncloud-db
    app.kubernetes.io/part-of: owncloud
  name: db
  namespace: owncloud
spec:
  selector:
    matchLabels:
      app.kubernetes.io/instance: owncloud
      app.kubernetes.io/managed-by: argocd
      app.kubernetes.io/name: owncloud-db
      app.kubernetes.io/part-of: owncloud
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app.kubernetes.io/instance: owncloud
        app.kubernetes.io/managed-by: argocd
        app.kubernetes.io/name: owncloud-db
        app.kubernetes.io/part-of: owncloud
    spec:
      containers:
      - args:
        - --default-authentication-plugin=mysql_native_password
        env:
        - name: MYSQL_DATABASE
          value: owncloud
        - name: MYSQL_USER
          value: owncloud
        - name: MYSQL_PASSWORD
          valueFrom:
            secretKeyRef:
              key: MYSQL_PASSWORD
              name: owncloud-db
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              key: MYSQL_ROOT_PASSWORD
              name: owncloud-db
        image: mysql:8
        livenessProbe:
          exec:
            command:
            - /usr/bin/mysqladmin
            - -u root
            - --password="${MYSQL_ROOT_PASSWORD}"
            - ping
          initialDelaySeconds: 5
          periodSeconds: 30
          timeoutSeconds: 10
        name: owncloud-db
        ports:
        - containerPort: 3306
          name: mysql
        readinessProbe:
          exec:
            command:
            - /usr/bin/mysqladmin
            - -u root
            - --password="${MYSQL_ROOT_PASSWORD}"
            - ping
          initialDelaySeconds: 5
          periodSeconds: 30
          timeoutSeconds: 10
        volumeMounts:
        - mountPath: /var/lib/mysql
          name: owncloud-db
        - mountPath: /var/lib/backup
          name: owncloud-db-backup
      volumes:
      - hostPath:
          path: /geminos/kubernetes/owncloud/db
        name: owncloud-db
      - hostPath:
          path: /geminos/kubernetes/owncloud/db-backup
        name: owncloud-db-backup

tmuxのステータスラインにk8sのvwntextとnamespaceを表示する

複数のk8sクラスタに接続する頻度が増えてきたので、現在どのコンテキストへ接続しているのかが分かりやすくtmuxのステータスラインに表示するようにしました.

f:id:corrupt952:20210127074407p:plain

この画像で言えば(kind-ira/owncloud)が、それぞれ現在選択するContextとNamespaceです.
実際の設定は、

set-option -g status-right "#[fg=colour33]#(/bin/bash ${HOME}/bin/tmux-kubectl) #[fg=green][#S:#I.#P]"

と定義し、スクリプトの実行結果をステータスラインに表示しています.
細かいケースを考慮できていないかもしれませんが、スクリプトは以下のように定義しています.

#!/usr/bin/env bash

if [[ -z "$(which kubectl 2>/dev/null)" ]]; then
    echo "kubectl doesn't exist"
    exit 1
fi

kubeconfig="$HOME/.kube/config"
if [[ -n "${KUBECONFIG}" ]]; then
    kubeconfig=${KUBECONFIG}
fi

context="$(kubectl config current-context 2>/dev/null)"
if [[ -z "${context}" ]]; then
    echo "current-context doesn't exist"
    exit 1
fi

namespace="$(kubectl config view -o "jsonpath={.contexts[?(@.name==\"${context}\")].context.namespace}" 2>/dev/null)"
[[ -z "${namespace}" ]] && namespace="default"

echo "(${context}/${namespace})"

今後、使っていく時に何か不都合があれば少しずつ改善していきますか.

Lambdaの関数名とランタイムの一覧を取得する

今朝、Lambdaの関数名とランタイムの一覧を取得するPythonスクリプト書いたので貼っておきます.
シェルの実装でも良かったが、これをベースに他のスクリプトを組む予定なのでboto3で実装してみました.

#!/usr/bin/env python3

import json
import boto3

def aws_region_names():
    ec2 = boto3.client('ec2')
    regions = ec2.describe_regions()['Regions']
    return sorted([regions[_]['RegionName'] for _ in range(len(regions))])

def lambda_functions(region_name):
    client = boto3.client('lambda', region_name=region_name)
    functions = client.list_functions()['Functions']
    return [{ 'Name': functions[_]['FunctionName'], 'Runtime': functions[_]['Runtime']} for _ in range(len(functions))]

if __name__ == '__main__':
    region_functions = {}
    for region_name in aws_region_names():
        region_functions[region_name] = lambda_functions(region_name)
    print(json.dumps(region_functions))

処理の流れは書くまでもないですが、

  • リージョン一覧取得
  • リージョンごとのLambda Function一覧取得

をしています.
処理自体は簡単だが、関数名とランタイムを抽出しているリスト内包表記は大分見づらいので改善の余地はありそうです.