zaki work log

作業ログやら生活ログやらなんやら

[Kubernetes] MetalLBがHelmを使ったインストールに対応してたのでお試し(on kind)

任意のKubernetesクラスタでLoadBalancerタイプのServiceを使えるようにできるMetalLBが0.10.0からHelmを使ったインストールに対応してるようなので試してみた。

以前書いたMetalLBって何?的なエントリはこちら。

zaki-hmkc.hatenablog.com

環境

Helm は3.6.2

$ helm version --short
v3.6.2+gee407bd

Kubernetesクラスタはkindを使ってv1.21.1で作成。

$ kubectl get node
NAME                 STATUS   ROLES                  AGE   VERSION
july-control-plane   Ready    control-plane,master   75s   v1.21.1
july-worker          Ready    <none>                 40s   v1.21.1
july-worker2         Ready    <none>                 40s   v1.21.1

kindクラスタ自体はtype:LoadBalancer Serviceに対応してないので、デプロイしようとしてもこの通り使えない。(pendingのまま)

$ kc get pod,svc -n sample-app 
NAME                               READY   STATUS              RESTARTS   AGE
pod/sample-http-6c94f59975-7bskg   0/1     ContainerCreating   0          3s
pod/sample-http-6c94f59975-fvjcr   1/1     Running             0          3s

NAME                   TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
service/sample-http2   LoadBalancer   10.96.41.222   <pending>     80:32215/TCP   3s

MetalLBインストール with Helm

手順は公式ドキュメントの通り。
https://metallb.universe.tf/installation/#installation-with-helm

リポジトリ追加

[zaki@cloud-dev ~]$ helm repo add metallb https://metallb.github.io/metallb
"metallb" has been added to your repositories
[zaki@cloud-dev ~]$ helm repo list
NAME                    URL                                               
prometheus-community    https://prometheus-community.github.io/helm-charts
rook-release            https://charts.rook.io/release                    
stable                  https://charts.helm.sh/stable                     
grafana                 https://grafana.github.io/helm-charts             
elastic                 https://helm.elastic.co                           
metallb                 https://metallb.github.io/metallb        

チャートとデフォルト値の確認

インストールできるチャートはこんな感じ。

[zaki@cloud-dev ~]$ helm search repo metallb
NAME            CHART VERSION   APP VERSION     DESCRIPTION                                       
metallb/metallb 0.10.2          v0.10.2         A network load-balancer implementation for Kube...
stable/metallb  0.12.1          0.8.1           DEPRECATED MetalLB is a load-balancer implement...

デフォルト値は以下で確認できる。

$ helm show values metallb/metallb

SecurityContextとか微妙に違うけど、値はここの内容かな?
https://github.com/metallb/metallb/blob/main/charts/metallb/values.yaml

インストール

カスタマイズYAMLでtype:LoadBalancer Serviceに割り当てるアドレスを設定できるのでファイルを作成。
従来のConfigMapの設定だとアドレスレンジを記述するけど、HelmのカスタマイズYAMLはネットワークアドレスとマスクを記載するようになってる。(従来の書き方の可否は試してない)

ちなみにkindクラスタでLoadBalancerに使えるアドレスレンジは、docker network lsで確認できるkindって名前のDockerのブリッジネットワークのアドレスをdocker network inspect kindで確認できるので、そこから余りそうなアドレスを使うってやりかたで合ってたっぽい。

手元の環境のkindクラスタで使うDockerコンテナのアドレス設定はこんな感じ。

[zaki@cloud-dev ~]$ docker network inspect -f '{{.IPAM.Config}}' kind
[{172.19.0.0/16  172.19.0.1 map[]} {fc00:f853:ccd:e793::/64  fc00:f853:ccd:e793::1 map[]}]

これを元にkindクラスタ用のアドレス設定。

configInline:
  address-pools:
  - name: default
    protocol: layer2
    addresses:
    - 172.19.255.0/24

このファイルを指定してHelmチャートをインストール

[zaki@cloud-dev ~]$ helm install metallb metallb/metallb -f ~/local/kind/helm/values/values.yaml 
W0702 07:42:47.328620   36865 warnings.go:70] policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+
W0702 07:42:47.331087   36865 warnings.go:70] policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+
W0702 07:42:47.371780   36865 warnings.go:70] policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+
W0702 07:42:47.372231   36865 warnings.go:70] policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+
NAME: metallb
LAST DEPLOYED: Fri Jul  2 07:42:47 2021
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
MetalLB is now running in the cluster.
LoadBalancer Services in your cluster are now available on the IPs you
defined in MetalLB's configuration:

config:
  address-pools:
  - addresses:
    - 172.19.255.0/24
    name: default
    protocol: layer2

To see IP assignments, try `kubectl get services`.

あ、defaultネームスペースにデプロイされてる。

[zaki@cloud-dev ~]$ kubectl get pod,cm -n default
NAME                                      READY   STATUS    RESTARTS   AGE
pod/metallb-controller-748756655f-b9r85   1/1     Running   0          35s
pod/metallb-speaker-mn59v                 1/1     Running   0          35s
pod/metallb-speaker-rttht                 1/1     Running   0          35s
pod/metallb-speaker-wdkqn                 1/1     Running   0          35s

NAME                         DATA   AGE
configmap/kube-root-ca.crt   1      40m
configmap/metallb            1      35s
[zaki@cloud-dev ~]$ kubectl get pod,svc -n sample-app
NAME                               READY   STATUS    RESTARTS   AGE
pod/sample-http-6c94f59975-7bskg   1/1     Running   0          84s
pod/sample-http-6c94f59975-fvjcr   1/1     Running   0          84s

NAME                   TYPE           CLUSTER-IP     EXTERNAL-IP    PORT(S)        AGE
service/sample-http2   LoadBalancer   10.96.41.222   172.19.255.0   80:32215/TCP   84s

[zaki@cloud-dev ~]$ curl 172.19.255.0
<html><body><h1>It works!</h1></body></html>

動いた。

MetalLB本体をデプロイするネームスペースはdefaultでも良いのね。

カスタマイズYAML無しでインストールした場合

カスタマイズYAML無しでインストールした場合はConfigMapが作成されないので、そのままではtype:LoadBalancer Serviceは作成できないけど、あとからでもConfigMapを作成すればOK

apiVersion: v1
kind: ConfigMap
metadata:
  name: metallb
data:
  config: |
    address-pools:
    - name: default
      protocol: layer2
      addresses:
      - 172.19.255.0-172.19.255.50

ただ、従来の手動でインストールした場合と、ConfigMapのリソース名が異なるので注意。(以前はconfigだったはずだけど、Helm版はmetallbになってる)


サンプルに使ったhttpサーバーのマニフェストはこれ。

github.com

[Ansible / AWS] ダイナミックインベントリを使ってEC2接続先を動的に取得してAnsible実行お試し

Ansibleのダイナミックインベントリを使ったEC2アクセスについて、以前試したような試してないような記憶がおぼろげで、メモも無かったので簡単にまとめてみた。

docs.ansible.com

必要パッケージ

$ pip install boto3 botocore

クラウド上のEC2状態

こんな感じでpublic ipありでAmazon LinuxのEC2が3台動いてる。

f:id:zaki-hmkc:20210621085524p:plain

今回はダイナミックインベントリを使って、このEC2の「IPアドレスをチェックしてインベントリに指定する」という作業を省略する方法について確認する。

アクセスキー設定

後述のインベントリファイルのパラメタにaws_access_keyaws_secret_keyはあるけど、今回はansible-playbookを実行する側に環境変数で設定。

AWS_SECRET_ACCESS_KEY=****
AWS_ACCESS_KEY_ID=****

インベントリファイル

見落としててプチハマりしたんだけど、インベントリファイル名は指定があり、aws_ec2.ymlまたはaws_ec2.yamlで終わるファイル名でなければならない。

Uses a YAML configuration file that ends with aws_ec2.(yml|yaml).

これを踏まえてインベントリのファイル名はaws_ec2.ymlや、分かりやすいようにinventory_aws_ec2.ymlなどにしておく。
プラグインamazon.awsコレクションのaws_ec2インベントリプラグインを指定する。これはamazon.awsコレクションをansible-galaxy collection install amazon.awsでインストールすれば使用できる。
(Ansibleのインストールをcoreのみでなくフルセットで入れてれば付属しているはず)

---
plugin: amazon.aws.aws_ec2

最低限動かすにはこれだけの記述と、トークン情報を環境変数で設定しておけば、

$ ansible-inventory -i inventory-aws_ec2.yml --graph
@all:
  |--@aws_ec2:
  |  |--ec2-13-112-***-***.ap-northeast-1.compute.amazonaws.com
  |  |--ec2-13-113-***-***.ap-northeast-1.compute.amazonaws.com
  |  |--ec2-18-179-***-***.ap-northeast-1.compute.amazonaws.com
  |--@ungrouped:
$

このようにホストグループallでEC2のホスト情報を取得できるようになる。
リージョン指定しないと全リージョンなめてる感じなのかそこそこ時間かかるので、以下のようにリージョンを特定できるなら指定しておけば速度が少し上がる。

---
plugin: amazon.aws.aws_ec2
regions:
  - ap-northeast-1

プレイブック

allまたはaws_ec2グループは使えるので、これでお試し。

---
- hosts: all
  gather_facts: false

  tasks:
    - name: ping
      ping:
$ ansible-playbook -i inventory-aws_ec2.yml --user ec2-user --private-key=~/.ssh/id_aws_terraform playbook.yml 

PLAY [all] ************************************************************************************************************

TASK [ping] ***********************************************************************************************************
[WARNING]: Platform linux on host ec2-18-179-***-***.ap-northeast-1.compute.amazonaws.com is using the discovered Python
interpreter at /usr/bin/python, but future installation of another Python interpreter could change the meaning of that
path. See https://docs.ansible.com/ansible/2.10/reference_appendices/interpreter_discovery.html for more information.
ok: [ec2-18-179-***-***.ap-northeast-1.compute.amazonaws.com]
[WARNING]: Platform linux on host ec2-13-113-***-***.ap-northeast-1.compute.amazonaws.com is using the discovered Python
interpreter at /usr/bin/python, but future installation of another Python interpreter could change the meaning of that
path. See https://docs.ansible.com/ansible/2.10/reference_appendices/interpreter_discovery.html for more information.
ok: [ec2-13-113-***-***.ap-northeast-1.compute.amazonaws.com]
[WARNING]: Platform linux on host ec2-13-112-***-***.ap-northeast-1.compute.amazonaws.com is using the discovered
Python interpreter at /usr/bin/python, but future installation of another Python interpreter could change the meaning
of that path. See https://docs.ansible.com/ansible/2.10/reference_appendices/interpreter_discovery.html for more
information.
ok: [ec2-13-112-***-***.ap-northeast-1.compute.amazonaws.com]

PLAY RECAP ************************************************************************************************************
ec2-13-112-***-***.ap-northeast-1.compute.amazonaws.com : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
ec2-13-113-***-***.ap-northeast-1.compute.amazonaws.com : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
ec2-18-179-***-***.ap-northeast-1.compute.amazonaws.com : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

(a2.10) [zaki@cloud-dev aws (master)]$ 

Pythonインタプリタのパスについての警告がでてるけど、SSHアクセスしてpingモジュールの実行に成功することを確認。

ホストグループ指定の設定

今回はタグを使ってみる。

現状

  • bastion-0
  • app
  • db

の3種のNameタグがつけられたEC2インスタンスがあるので、(各タグが設定されたEC2は1台ずつしかないので面白くないけど)タグ名を使ってホストグループ設定できるようにしてみる。

設定はkeyed_groupsでキー名にtagsとどのタグを使うかを指定する。今回はNameタグなのでtags.Nameを指定。

---
plugin: amazon.aws.aws_ec2
regions:
  - ap-northeast-1
keyed_groups:
  - key: tags.Name
$ ansible-inventory -i inventory-aws_ec2.yml --graph
@all:
  |--@_app:
  |  |--ec2-13-112-***-***.ap-northeast-1.compute.amazonaws.com
  |--@_bastion_0:
  |  |--ec2-13-113-***-***.ap-northeast-1.compute.amazonaws.com
  |--@_db:
  |  |--ec2-18-179-***-***.ap-northeast-1.compute.amazonaws.com
  |--@aws_ec2:
  |  |--ec2-13-112-***-***.ap-northeast-1.compute.amazonaws.com
  |  |--ec2-13-113-***-***.ap-northeast-1.compute.amazonaws.com
  |  |--ec2-18-179-***-***.ap-northeast-1.compute.amazonaws.com
  |--@ungrouped:
$

この通り、Nameタグを使ったグルーピングができるようになる。
グループ名は_タグ名がデフォルトだけど、prefix: tagname_とか指定すれば、prefix指定ができる。

---
plugin: amazon.aws.aws_ec2
regions:
  - ap-northeast-1
keyed_groups:
  - key: tags.Name
    prefix: tagname_

この内容であれば、

$ ansible-inventory -i inventory-aws_ec2.yml --graph
@all:
  |--@aws_ec2:
  |  |--ec2-13-112-***-***.ap-northeast-1.compute.amazonaws.com
  |  |--ec2-13-113-***-***.ap-northeast-1.compute.amazonaws.com
  |  |--ec2-18-179-***-***.ap-northeast-1.compute.amazonaws.com
  |--@tagname__app:
  |  |--ec2-13-112-***-***.ap-northeast-1.compute.amazonaws.com
  |--@tagname__bastion_0:
  |  |--ec2-13-113-***-***.ap-northeast-1.compute.amazonaws.com
  |--@tagname__db:
  |  |--ec2-18-179-***-***.ap-northeast-1.compute.amazonaws.com
  |--@ungrouped:

こんな感じ。 あ、_が余計だな。

グループのキーとして指定できる一覧がぱっと見見つからないけど、Examplesを見た感じ、アーキテクチャインスタンスタイプ、リージョンなどいろいろ指定できそう。(AZは無い?)

  - key: architecture
  - key: instance_type
  - key: placement.region
$ ansible-inventory -i inventory-aws_ec2.yml --graph
@all:
  |--@_ap_northeast_1:
  |  |--ec2-13-112-***-***.ap-northeast-1.compute.amazonaws.com
  |  |--ec2-13-113-***-***.ap-northeast-1.compute.amazonaws.com
  |  |--ec2-18-179-***-***.ap-northeast-1.compute.amazonaws.com
  |--@_t3_nano:
  |  |--ec2-13-112-***-***.ap-northeast-1.compute.amazonaws.com
  |  |--ec2-13-113-***-***.ap-northeast-1.compute.amazonaws.com
  |  |--ec2-18-179-***-***.ap-northeast-1.compute.amazonaws.com
  |--@_x86_64:
  |  |--ec2-13-112-***-***.ap-northeast-1.compute.amazonaws.com
  |  |--ec2-13-113-***-***.ap-northeast-1.compute.amazonaws.com
  |  |--ec2-18-179-***-***.ap-northeast-1.compute.amazonaws.com
  |--@aws_ec2:
  |  |--ec2-13-112-***-***.ap-northeast-1.compute.amazonaws.com
  |  |--ec2-13-113-***-***.ap-northeast-1.compute.amazonaws.com
  |  |--ec2-18-179-***-***.ap-northeast-1.compute.amazonaws.com
  |--@tagname__app:
  |  |--ec2-13-112-***-***.ap-northeast-1.compute.amazonaws.com
  |--@tagname__bastion_0:
  |  |--ec2-13-113-***-***.ap-northeast-1.compute.amazonaws.com
  |--@tagname__db:
  |  |--ec2-18-179-***-***.ap-northeast-1.compute.amazonaws.com
  |--@ungrouped:

環境

$ ansible --version
ansible 2.10.7
  config file = /home/zaki/.ansible.cfg
  configured module search path = ['/home/zaki/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /home/zaki/src/ansible-sample/venv/a2.10/lib64/python3.6/site-packages/ansible
  executable location = /home/zaki/src/ansible-sample/venv/a2.10/bin/ansible
  python version = 3.6.8 (default, Nov 16 2020, 16:55:22) [GCC 4.8.5 20150623 (Red Hat 4.8.5-44)]

まとめ

aws_ec2ダイナミックインベントリを使うことで、EC2のIPアドレスを把握してなくても予め設定しておいたタグ名などを使ってターゲットノードとしてアクセスできることを確認できた。
クラウドのコンピュートインスタンスをリソースを使い捨てにするためIPアドレスを固定せずに使用する場合、クラウドサービスのAPIを使って動的にアドレスを取得する仕組みがあるのは便利。
対象のアドレスをダイナミックインベントリを使って取得した後の認証などは従来と同じなので、SSH公開鍵認証設定などを使ってアクセスする。

[Docker Compose] cpu / memory / ログサイズの制限を指定する

元ネタはこちら。

zaki-hmkc.hatenablog.com

zaki-hmkc.hatenablog.com

元ネタではDocker(dockerコマンド)単体で制限設定を行ってましたが、同じことをDocker Compose (v3)で指定する場合はどうするかについて。

docs.docker.com

cpu / memory

deploy/resourcesで設定します。
例えばCPUは0.3、メモリは512MBであれば以下の通り。

docker runであれば--cpus 0.3 -m 512mと同等。

    deploy:
      resources:
        limits:
          cpus: '0.3'
          memory: 512m

デプロイされたコンテナをinspectすると以下のように出力される。

        "HostConfig": {
            [...]
            "Memory": 536870912,
            "NanoCpus": 300000000,

ちなみに、Composeのリソース設定のマニュアル見るまで気付かなかったけど、Kubernetesrequestsと同じようなreservationsって指定もできるみたい?(動作未確認 / K8sと同じならその起動時に指定分のリソースが無かったら起動できない、かな?)

なお、この指定はDocker Compose File v3の書式。v2だと異なります。

ログサイズ

loggingで設定します。

ログドライバにjson-fileを使用し、そのサイズを10KBに制限するには以下の通り。
json-fileで使えるオプションについてはJSON File logging driverを参照。

    logging:
      driver: json-file
      options:
        max-size: 10k

これでデプロイされたコンテナをinspectすると、以下の内容を確認できる。

        "HostConfig": {
            [...]
            "LogConfig": {
                "Type": "json-file",
                "Config": {
                    "max-size": "10k"
                }
            },

json-file以外、例えばSyslogドライバであればそれぞれのドライバのドキュメントを参照。

docs.docker.com

(参考) 全体のDocker Composeファイル

version: '3'
services:
  my-httpd:
    image: httpd
    ports:
    - 24080:80
    deploy:
      resources:
        limits:
          cpus: '0.3'
          memory: 512m
    logging:
      driver: json-file
      options:
        max-size: 10k
  client:
    image: centos:7
    command:
    - "tail"
    - "-f"
    - "/dev/null"

[Linux] GitHubに登録している公開鍵を ~/.ssh/authorized_keys に取り込む

取り込むというか単にcurlで取得できるよ、という小ネタ。

少し前からUbuntuのOSインストール時にGitHubに登録してるSSH公開鍵を設定できるようになってたりしてますが、GitHubの公開鍵は特定のURLで簡単に参照できるので、OSインストール後でも任意のタイミングで設定できます。

GitHubの公開鍵のURL

GitHubに登録した公開鍵は以下のURLでアクセスできます。

https://github.com/{GITHUB_USERNAME}.keys

私(zaki-lknr)の場合だと以下。

https://github.com/zaki-lknr.keys

authorized_keysへ公開鍵の登録

OpenSSHの場合は、公開鍵は~/.ssh/authorized_keysに保存すればOK

$ curl https://github.com/zaki-lknr.keys -o /home/zaki/.ssh/authorized_keys

すでに~/.ssh/authorized_keysに別の公開鍵が登録してある場合は↑だと上書きしてしまうので、

$ curl https://github.com/zaki-lknr.keys >> /home/zaki/.ssh/authorized_keys

などで追記します。

kickstartインストールで設定する

以前ここで公開鍵を埋め込んで実装してたけど、OSインストール直後の状態でインターネットにアクセスできるのであればkickstartインストールの%postに組み込めます。

%post
mkdir -m 700 /home/zaki/.ssh
curl https://github.com/zaki-lknr.keys -o /home/zaki/.ssh/authorized_keys
chmod 600 /home/zaki/.ssh/authorized_keys
chown -R zaki:zaki /home/zaki/.ssh
%end

こんな感じ。

(ちなみに/.sshは755でauthorized_keysは644でもリモートから公開鍵認証できるなぁ…sshd設定かな)

zaki-hmkc.hatenablog.com

[Ansible] 省略したいパラメタに変数指定せざるを得ない場合に使う変数omitとdefaultフィルタ

指定が任意なオプション扱いのパラメタで、指定するパラメタが無い場合は未指定にしたいけど、ループ処理などで決まった型の辞書型変数のリストでキーが有ったり無かったりする場合の処理について。

って書いてもうまく伝わらない気もするのでサンプルコードから。

  - name: create files by vars
    vars:
      files:
        - path: /var/tmp/ansible/var_test
          type: directory
          owner: zaki
          group: zaki
          mode: '0755'
        - path: /var/tmp/ansible/var_test/file1
          type: touch
    ansible.builtin.file:
      state: "{{ item.type }}"
      path: "{{ item.path }}"
      mode: "{{ item.mode | default(omit) }}"
      owner: "{{ item.owner | default(omit) }}"
      group: "{{ item.group | default(omit) }}"
    loop: "{{ files }}"

files変数に、path/type/pwner/group/modeを持った辞書型変数のリストを定義してるけど、1個目のディレクトリ定義は全て指定してるのに対して、2個目のファイル定義はowner/group/modeの指定無しの状態。

これをループ使って1発でansible.builtin.file使ってファイル/ディレクトリ作成しようとしても、mode,owner,groupの定義は2要素目の項目に無いということで

      mode: "{{ item.mode }}"
      owner: "{{ item.owner }}"
      group: "{{ item.group }}"

とplaybookに書いても、2ループ目は以下のように、変数にmode/owner/groupの定義が無いのでエラーになってしまう。

  msg: |-
    The task includes an option with an undefined variable. The error was: 'dict object' has no attribute 'mode'

マジック変数omit

こういうときに使うのが、Ansibleのマジック変数omitで、omitが指定されたパラメタは無視され「パラメタ指定なし(省略)」として動作する。

docs.ansible.com

docs.ansible.com

omit単体の動作は以下のコードで確認できる。

  vars:
    data:
      j2_none: "{{ none }}"
      var_omit: "{{ omit }}"
      undef: undefined
      blank: ""
      tilde: ~
      nullval: null

  tasks:
  - name: print
    debug:
      msg: "{{ data }}"

この内容のplaybookで実行すると、出力は以下の通り。

ok: [localhost] => 
  msg:
    blank: ''
    j2_none: ''
    nullval: null
    tilde: null
    undef: undefined

omitをセットしていたvar_omit変数はきれいさっぱり無くなっているのを確認できる。

ついでに、Jinja2テンプレート内(というよりPythonかな?)でnoneを使ってもAnsibleで受け取るとnullになったりせず空文字になり、YAMLnull,~はAnsibleでもnullとなる。

動作はAnsible 2.9.16と2.10.7で確認。

サンプル

github.com

外部ファイル入力などで未定義を表現できないとき

上の例は「辞書で未定義の場合(keyが無い場合)はdefaultフィルタでomitをセット」だったが、例えばAPI実行の結果を入力にしたいけどデータが無い場合は空文字になってるとか、csvファイルを入力にしたいけどカラムは固定なのでデータ無しは空文字扱い、といったパターンもある。

defaultフィルタの通常動作は「フィルタの入力変数が未定義の場合は引数の変数をセット」という動作のため、変数は存在するけどセットされている変数が0(int)false(bool)や空文字やnullの場合は反応しない。
これらの「偽」の場合にdefaultフィルタを使って値をセットするには、引数の第2引数にtrueをセットする。

jinja.palletsprojects.com

If you want to use default with variables that evaluate to false you have to set the second parameter to true:

{{ ''|default('the string was empty', true) }}

docs.ansible.com

If you want to use the default value when variables evaluate to false or an empty string you have to set the second parameter to true:

{{ lookup('env', 'MY_USER') | default('admin', true) }}

これを使えば、入力csvファイルでファイルとプロパティ一覧を以下のように作成し、

path,type,owner,group,mode
/var/tmp/ansible/csv_test,directory,zaki,zaki,0755
/var/tmp/ansible/csv_test2,directory,,,
/var/tmp/ansible/csv_test/file1,touch,,,

playbookではansible.builtin.read_csvを使って

  tasks:
  - name: read csv file
    ansible.builtin.read_csv:
      path: filelist.csv
    register: res_input

  - name: create files by csv
    ansible.builtin.file:
      state: "{{ item.type }}"
      path: "{{ item.path }}"
      mode: "{{ item.mode | default(omit, true) }}"
      owner: "{{ item.owner | default(omit, true) }}"
      group: "{{ item.group | default(omit, true) }}"
    loop: "{{ res_input.list }}"

みたいな実装をすることで、modeやowner指定が無い場合は省略してファイル作成ができる。

サンプル

github.com


ちなみに、fileモジュールのownergroupnull指定してもエラーになるが、modeについてはnull指定するとパラメタは無視されてデフォルト動作になるっぽい。

docs.ansible.com

この辺りの動作はモジュールに依存するので動作はよく確認すること。

静的サイトジェネレータMkDocsのGetting Startedおためし

KubernetesドキュメントサイトではHugoというサイトジェネレータを使ったのですが、似たようなツールにMkDocsというものがあり、試してみた。
(そのうちGitHub Pagesに置きたい)

www.mkdocs.org

大まかな流れとしては、Markdown形式でドキュメントを指定のパスに置いて、mkdocs serveするとローカルでサーバー起動して動作確認でき、mkdocs buildするとhtmlファイルやcssなどが生成される、というもの。

テーマとかまだ何も設定してないけど、まずは公式のGetting Startedにそって試してみた。

インストール

pipでインストールする。

(mkdocs) [zaki@cloud-dev mkdocs]$ pip list
Package    Version
---------- -------
pip        21.1.2
setuptools 39.2.0
(mkdocs) [zaki@cloud-dev mkdocs]$ ls
venv
(mkdocs) [zaki@cloud-dev mkdocs]$ cat requirements.txt 
mkdocs
(mkdocs) [zaki@cloud-dev mkdocs]$ 
(mkdocs) [zaki@cloud-dev mkdocs]$ 
(mkdocs) [zaki@cloud-dev mkdocs]$ pip install -r requirements.txt 
Collecting mkdocs
  Downloading mkdocs-1.1.2-py3-none-any.whl (6.4 MB)
     |████████████████████████████████| 6.4 MB 4.6 MB/s 
Collecting lunr[languages]==0.5.8
  Downloading lunr-0.5.8-py2.py3-none-any.whl (2.3 MB)
     |████████████████████████████████| 2.3 MB 11.3 MB/s 
Collecting click>=3.3
  Downloading click-8.0.1-py3-none-any.whl (97 kB)
     |████████████████████████████████| 97 kB 9.4 MB/s 
Collecting Markdown>=3.2.1
  Downloading Markdown-3.3.4-py3-none-any.whl (97 kB)
     |████████████████████████████████| 97 kB 9.8 MB/s 
Collecting Jinja2>=2.10.1
  Using cached Jinja2-3.0.1-py3-none-any.whl (133 kB)
Collecting livereload>=2.5.1
  Downloading livereload-2.6.3.tar.gz (25 kB)
Collecting PyYAML>=3.10
  Using cached PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl (640 kB)
Collecting tornado>=5.0
  Downloading tornado-6.1-cp36-cp36m-manylinux2010_x86_64.whl (427 kB)
     |████████████████████████████████| 427 kB 10.9 MB/s 
Collecting six>=1.11.0
  Using cached six-1.16.0-py2.py3-none-any.whl (11 kB)
Collecting future>=0.16.0
  Downloading future-0.18.2.tar.gz (829 kB)
     |████████████████████████████████| 829 kB 11.2 MB/s 
Collecting nltk>=3.2.5
  Downloading nltk-3.6.2-py3-none-any.whl (1.5 MB)
     |████████████████████████████████| 1.5 MB 11.4 MB/s 
Collecting importlib-metadata
  Downloading importlib_metadata-4.2.0-py3-none-any.whl (16 kB)
Collecting MarkupSafe>=2.0
  Using cached MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl (30 kB)
Collecting joblib
  Downloading joblib-1.0.1-py3-none-any.whl (303 kB)
     |████████████████████████████████| 303 kB 10.4 MB/s 
Collecting regex
  Downloading regex-2021.4.4-cp36-cp36m-manylinux2014_x86_64.whl (722 kB)
     |████████████████████████████████| 722 kB 10.5 MB/s 
Collecting tqdm
  Downloading tqdm-4.61.0-py2.py3-none-any.whl (75 kB)
     |████████████████████████████████| 75 kB 8.5 MB/s 
Collecting zipp>=0.5
  Using cached zipp-3.4.1-py3-none-any.whl (5.2 kB)
Collecting typing-extensions>=3.6.4
  Using cached typing_extensions-3.10.0.0-py3-none-any.whl (26 kB)
Using legacy 'setup.py install' for future, since package 'wheel' is not installed.
Using legacy 'setup.py install' for livereload, since package 'wheel' is not installed.
Installing collected packages: zipp, typing-extensions, importlib-metadata, tqdm, six, regex, joblib, future, click, tornado, nltk, MarkupSafe, lunr, PyYAML, Markdown, livereload, Jinja2, mkdocs
    Running setup.py install for future ... done
    Running setup.py install for livereload ... done
Successfully installed Jinja2-3.0.1 Markdown-3.3.4 MarkupSafe-2.0.1 PyYAML-5.4.1 click-8.0.1 future-0.18.2 importlib-metadata-4.2.0 joblib-1.0.1 livereload-2.6.3 lunr-0.5.8 mkdocs-1.1.2 nltk-3.6.2 regex-2021.4.4 six-1.16.0 tornado-6.1 tqdm-4.61.0 typing-extensions-3.10.0.0 zipp-3.4.1
(mkdocs) [zaki@cloud-dev mkdocs]$ 

お試し

プロジェクト作成

my-projectプロジェクトを作成

(mkdocs) [zaki@cloud-dev mkdocs]$ mkdocs new my-project
INFO    -  Creating project directory: my-project 
INFO    -  Writing config file: my-project/mkdocs.yml 
INFO    -  Writing initial docs: my-project/docs/index.md 
(mkdocs) [zaki@cloud-dev mkdocs]$ ls
my-project  requirements.txt  venv
(mkdocs) [zaki@cloud-dev mkdocs]$ cd my-project/
(mkdocs) [zaki@cloud-dev my-project]$ ls
docs  mkdocs.yml

動作確認用サーバー起動

(mkdocs) [zaki@cloud-dev my-project]$ mkdocs serve
INFO    -  Building documentation... 
INFO    -  Cleaning site directory 
INFO    -  Documentation built in 0.05 seconds 
[I 210527 09:11:55 server:335] Serving on http://127.0.0.1:8000
INFO    -  Serving on http://127.0.0.1:8000

[Errno 98] Address already in use

8000/TCPとか他で使ってるわ。。

(mkdocs) [zaki@cloud-dev my-project]$ mkdocs serve --help
Usage: mkdocs serve [OPTIONS]

  Run the builtin development server

Options:
  -a, --dev-addr <IP:PORT>        IP address and port to serve documentation
                                  locally (default: localhost:8000)
  --livereload                    Enable the live reloading in the development
                                  server (this is the default)
  --no-livereload                 Disable the live reloading in the
                                  development server.
  --dirtyreload                   Enable the live reloading in the development
                                  server, but only re-build files that have
                                  changed
  -f, --config-file FILENAME      Provide a specific MkDocs config
  -s, --strict                    Enable strict mode. This will cause MkDocs
                                  to abort the build on any warnings.
  -t, --theme [mkdocs|readthedocs]
                                  The theme to use when building your
                                  documentation.
  --use-directory-urls / --no-directory-urls
                                  Use directory URLs when building pages (the
                                  default).
  -q, --quiet                     Silence warnings
  -v, --verbose                   Enable verbose output
  -h, --help                      Show this message and exit.

helpを見るとポート単体はなさそうだけど、-aでlistenするアドレスとポート指定できるみたい。
ついでなのでリモートからも接続できるように0.0.0.0で起動。
(firewalld空けないといけないので結局これ使わずにsshのポートフォワードしたけど…)

警告も出てるけど、0.0.0.0はアクセス範囲広くていろいろ注意必要なので、ポートフォワードするなら127.0.0.1:8081で問題ない。

(mkdocs) [zaki@cloud-dev my-project]$ mkdocs serve -a 0.0.0.0:8081
INFO    -  Building documentation... 
WARNING -  Config value: 'dev_addr'. Warning: The use of the IP address '0.0.0.0' suggests a production environment or the use of a proxy to connect to the MkDocs server. However, the MkDocs' server is intended for local development purposes only. Please use a third party production-ready server instead. 
INFO    -  Cleaning site directory 
INFO    -  Documentation built in 0.05 seconds 
[I 210527 09:13:51 server:335] Serving on http://0.0.0.0:8081
INFO    -  Serving on http://0.0.0.0:8081
[I 210527 09:13:51 handlers:62] Start watching changes
INFO    -  Start watching changes
[I 210527 09:13:51 handlers:64] Start detecting changes
INFO    -  Start detecting changes

実行するとこの通り、フォアグラウンドで動作し、docs以下のMarkdownファイルを元にwebアクセス経由でページ表示できるようになる。

プロジェクト作成時に自動で生成されるインデックスページは以下の通り。

# Welcome to MkDocs

For full documentation visit [mkdocs.org](https://www.mkdocs.org).

## Commands

* `mkdocs new [dir-name]` - Create a new project.
* `mkdocs serve` - Start the live-reloading docs server.
* `mkdocs build` - Build the documentation site.
* `mkdocs -h` - Print help message and exit.

## Project layout

    mkdocs.yml    # The configuration file.
    docs/
        index.md  # The documentation homepage.
        ...       # Other markdown pages, images and other files.

このままサーバー起動しブラウザでアクセスすると以下のように表示される。

f:id:zaki-hmkc:20210527095414p:plain

ページ追加

docs/about.mdを追加してみる。
内容は以下の通り。

# about

@zaki_hmkc

サーバーは起動したままにしておくと、ファイルの変更を自動で検知し動的にページ更新してくれる。

[I 210527 09:18:27 watcher:111] Running task: builder (delay: None)
INFO    -  Running task: builder (delay: None)
INFO    -  Building documentation... 
WARNING -  Config value: 'dev_addr'. Warning: The use of the IP address '0.0.0.0' suggests a production environment or the use of a proxy to connect to the MkDocs server. However, the MkDocs' server is intended for local development purposes only. Please use a third party production-ready server instead. 
[I 210527 09:18:27 handlers:95] Reload 1 waiters: /home/zaki/local/mkdocs/my-project/docs/about.md
INFO    -  Reload 1 waiters: /home/zaki/local/mkdocs/my-project/docs/about.md
[I 210527 09:18:28 handlers:135] Browser Connected: http://localhost:8081/
INFO    -  Browser Connected: http://localhost:8081/
[I 210527 09:18:59 watcher:111] Running task: builder (delay: None)
INFO    -  Running task: builder (delay: None)
INFO    -  Building documentation... 
WARNING -  Config value: 'dev_addr'. Warning: The use of the IP address '0.0.0.0' suggests a production environment or the use of a proxy to connect to the MkDocs server. However, the MkDocs' server is intended for local development purposes only. Please use a third party production-ready server instead. 
[I 210527 09:18:59 handlers:95] Reload 1 waiters: /home/zaki/local/mkdocs/my-project/docs/about.md
INFO    -  Reload 1 waiters: /home/zaki/local/mkdocs/my-project/docs/about.md
[I 210527 09:18:59 handlers:135] Browser Connected: http://localhost:8081/
INFO    -  Browser Connected: http://localhost:8081/
[I 210527 09:19:06 handlers:135] Browser Connected: http://localhost:8081/
INFO    -  Browser Connected: http://localhost:8081/

生成されたページはこの通り。

f:id:zaki-hmkc:20210527095355p:plain

f:id:zaki-hmkc:20210527095337p:plain

サイト名の変更

ページ名はテンプレート生成したときの初期値はMy Docsになっているが、ルートのmkdocs.ymlで指定可能。

site_name: mkdocsお試し

この内容に変更すると、表示は以下の通り。

f:id:zaki-hmkc:20210527095309p:plain

サイトのビルド

(mkdocs) [zaki@cloud-dev my-project]$ ls
docs  mkdocs.yml
(mkdocs) [zaki@cloud-dev my-project]$ mkdocs build
INFO    -  Cleaning site directory 
INFO    -  Building documentation to directory: /home/zaki/local/mkdocs/my-project/site 
INFO    -  Documentation built in 0.05 seconds 
(mkdocs) [zaki@cloud-dev my-project]$ ls
docs  mkdocs.yml  site

mkdocs buildを実行すると、(デフォルトでは)siteディレクトリ以下にMarkdownを元にwebページ用のhtml他のファイルが作成される。

(mkdocs) [zaki@cloud-dev my-project]$ ls -F site/
404.html  about/  css/  fonts/  img/  index.html  js/  search/  sitemap.xml  sitemap.xml.gz

このファイルをwebサーバーに置けば、動作確認じに表示されたコンテンツにアクセスできる。
暫定なので(firewalldの設定変更なので)Dockerのhttpdで…

(mkdocs) [zaki@cloud-dev my-project]$ docker run --rm -p 8089:80 -v $PWD/site:/usr/local/apache2/htdocs -d httpd
e03d10f5a697f8e830a6d3c1403ce45f4e3e231539852f0c20a5f82dca878aaf

リモートからwebサーバーにアクセスするとこの通り。

f:id:zaki-hmkc:20210527095541p:plain

環境


本編と関係ないけど「このディレクトリのファイルをwebサーバー経由でアクセスしたい」はDocker使うと本当に楽だなぁ

[Ansible] ターゲットノードのvenvのPythonを指定して実行する (interpreter_python / ansible_python_interpreter / PYTHONPATH)

コントロールノードでなくターゲットノードで(pipで追加パッケージが必要などの理由で)venvを使いたい場合の方法について。
といっても、venvの中にあるPythonインタプリタのパスをinterpreter_pythonなどで指定すればOK。

Python2/Python3の切り替えでインタプリタを指定するという趣旨の記事が多いというか自分も以前書いてたりするけど、Pythonの仮想環境の指定にも使えたので動作確認した結果をまとめた。

ちなみに「『ターゲットノードでvenvを使う』というドンピシャな情報」を見つけられなかっただけなので、もしこの目的のための設定項目が別にあるなら教えてください…

お題はここでも使ったcommunity.kubernetesコレクションに含まれるk8s_cluster_infoを使ってクラスタ情報を得るというもの。

docs.ansible.com

環境

ansible-playbookを実行するコントロールノードはCentOS 7で、処理する接続先のターゲットノードはFedora 34。
ターゲットのPythonは、デフォルトでは/usr/bin/pythonが参照されるけど、k8s_infoの実行に必要なopenshiftパッケージはシステムワイドにはインストールせずに/home/zaki/tmp/venvに作った仮想環境に入れている、という状態。

ベースとなるplaybookは以下の通り。

---
- hosts: linux
  gather_facts: false

  tasks:
    - name: get cluster info
      community.kubernetes.k8s_cluster_info:
      register: result

    - name: print result
      debug:
        msg: "{{ result.version }}"

以下、いくつかの方法を書いてるけど、どれか一つを採用すればOK

ansible.cfg

defaultsセクションのinterpreter_pythonpythonのパスを指定する

[defaults]
interpreter_python = /home/zaki/tmp/venv/bin/python

全てのターゲットノードでパスが同じであればこれが一番楽。

ターゲットノード毎に設定

ホスト変数を使えばOK
ansible.cfgと異なり、指定する変数名はansible_python_interpreterにパスをセットする。

[linux]
fedora-node ansible_python_interpreter=/home/zaki/tmp/venv/bin/python

ターゲットノードによってvenvのパスが異なる場合で、全ての処理で常に同じPythonインタプリタを使うのであればこれで。

playやtask単位

ホスト変数と同じ要領で、playやtaskの定義でvarsなどを使って変数にパス指定すればOK

    - name: get cluster info
      community.kubernetes.k8s_cluster_info:
      vars:
        ansible_python_interpreter: /home/zaki/tmp/venv/bin/python
      register: result

処理(taskやrole)によって個別にインタプリタを切り替えたい場合はこれで指定する。

パッケージパスの指定

Pythonインタプリタでなく、環境変数PYTHONPATHにパッケージのパスを指定する方法でもいける。

    - name: get cluster info
      community.kubernetes.k8s_cluster_info:
      environment:
        PYTHONPATH: /home/zaki/venv/lib/python3.9/site-packages/
      register: result

ただしパスを見ての通りPythonのサブバージョンまでディレクトリ名に含まれてるので環境情報を把握しておく必要がある。


ちなみにansible-playbookの実行結果はこんな感じ。
戻り値の詳細はドキュメント参照。

ok: [fedora-node] => 
  msg:
    client: 0.12.0
    server:
      kubernetes:
        buildDate: '2021-01-21T01:11:42Z'
        compiler: gc
        gitCommit: faecb196815e248d3ecfb03c680a4507229c2a56
        gitTreeState: clean
        gitVersion: v1.20.2
        goVersion: go1.15.5
        major: '1'
        minor: '20'
        platform: linux/amd64

サンプルコード

github.com