docker コンテナで crond を使い aws cli を定期的に実行する

docker-compose で起動させたコンテナ上で定期的に
aws cli を実行させたかったので試してみました。

ファイル構成

$ ls
Dockerfile  crontab  docker-compose.yml

Dockerfile

定期的な処理には busybox crond を使います。
update-ca-certificates しているのは
curlcurl: (60) SSL certificate problem: unable to get local issuer certificate エラーを解決するためです。

docker-compose.yml

aws cli の実行結果を保存するためにホストマシンの ./work
コンテナの /aws にマウントします。
aws cli環境変数もここで設定します。

crontab

1 分毎に aws cli を実行し、マウントしたディレクトリに結果を出力してみます。

コンテナを起動してみる

ちゃんと処理できてます。
後で知ったのですが、mattbanner/aws-cli-cron というイメージも既にあるみたいです。

$ docker-compose up -d --no-deps --build
$ docker-compose logs -f
Attaching to cronaws_cron-aws_1
cron-aws_1  | crond: crond (busybox 1.30.1) started, log level 8
cron-aws_1  | crond: USER root pid   7 cmd aws s3 ls >> /aws/log
cron-aws_1  | crond: USER root pid  13 cmd aws s3 ls >> /aws/log
$ docker-compose down
$ cat work/log 
2021-04-20 10:00:01 fugafuga-bucket-1
2021-04-20 10:00:01 fugafuga-bucket-1

参考にさせていただいたサイト

join コマンドの練習

join コマンドを使ったファイルの結合を練習してみます。
テスト用にファイル breeds と dogs を用意しました。
2 つとも 1 行目はヘッダです。データはソートされていないです。

$ cat breeds 
id breed
4 maltese
1 poodle
3 frenchbulldog
2 pomeranian
5 husky 

$ cat dogs 
id name breed_id
2 koro 5 
1 sofia 4
3 louis 3

結合できなかった行を表示しない場合(内部結合?)

後ほど出てきますが -a オプションを付けないと
結合できなかった行は表示されません。

まずは dogs の breed_id で結合してみます。
join コマンドでは対象データがソートされている必要があるので
sort した結果を join に渡しています。ヘッダ行は邪魔なので
sort 前に sed '1d' で消しています。
join の -1 3 -2 1 は 1 つめのファイルの 3 つめのフィールドと
2 つめのファイルの 1 つめのフィールドを結合するという意味です。

$ join -1 3 -2 1 <(sed '1d' dogs | sort -k3) <(sed '1d' breeds | sort)
3 3 louis frenchbulldog
4 1 sofia maltese
5 2 koro  husky 

join で -o を使用すると表示するフィールドを選択できます。
1.1 は 1 つめのファイルの 1 つめのフィールドという意味です。

$ join -1 3 -2 1 -o 1.1 1.2 1.3 2.2 <(sed '1d' dogs | sort -k3) <(sed '1d' breeds | sort)
3 louis 3 frenchbulldog
1 sofia 4 maltese
2 koro 5 husky

今度は breeds の id で結合してみます。-a オプションが無いので
poodle, pomeranian は表示されません。

$ join -1 1 -2 3 -o 1.1 1.2 2.1 2.2 <(sed '1d' breeds | sort) <(sed '1d' dogs | sort -k3)
3 frenchbulldog 3 louis
4 maltese 1 sofia
5 husky 2 koro

結合できなかった行を表示する場合(外部結合?)

上記の poodle, pomeranian が表示されなかったコマンドに -a 1 を付けて
実行してみます。-a 1 -a 2 という使い方もできるらしいです。
-e で一致するフィールドが無い時に表示する文字列を指定します。

$ join -a 1 -1 1 -2 3 -o 1.1 1.2 2.1 2.2 -e NULL <(sed '1d' breeds | sort) <(sed '1d' dogs | sort -k3)
1 poodle NULL NULL
2 pomeranian NULL NULL
3 frenchbulldog 3 louis
4 maltese 1 sofia
5 husky 2 koro

参考にさせていただいたサイト

サービスのログが journald に収集されるか試す

journald について勉強しています。systemd が起動するサービスの標準出力と標準エラー出力が journald に収集されるか、 テスト用のログを出力するサービスを作り、試してみました。

テスト用のログ出力スクリプト

以下のスクリプト/home/pi/testsh/test.sh に配置しました。
10 秒ごとに標準出力と標準エラー出力に書き込みます。

#!/bin/bash
while true
do
   sleep 10
   echo "hello stdout"
   echo "hello stderr" >&2
done

ユニットファイルを作る

test.sh を実行するサービスのユニットファイルを /etc/systemd/system/test.service に作ります。
参考にさせてもらったサイト: https://qiita.com/DQNEO/items/0b5d0bc5d3cf407cb7ff

$ sudoedit /etc/systemd/system/test.service
$ cat /etc/systemd/system/test.service
[Unit]
Description = test

[Service]
ExecStart = /home/pi/testsh/test.sh
Restart = always
Type = simple

[Install]
WantedBy = multi-user.target

test.service がサービスとして認識されたか確認する

OK ですね。

$ sudo systemctl list-unit-files --type=service | grep test
test.service                               disabled    

サービスを起動して、journald のログを確認する

サービス起動前に journalctl | grep hello で journald のログに hello stdouthello stderr が無いことを確認します。

$ journalctl | grep hello

サービスを起動します。

$ sudo systemctl start test.service

再度ログを確認すると、ちゃんと出力されています。

$ journalctl | grep hello
 4月 04 17:24:28 raspberrypi test.sh[13035]: hello stdout
 4月 04 17:24:28 raspberrypi test.sh[13035]: hello stderr
 4月 04 17:24:38 raspberrypi test.sh[13035]: hello stdout
 4月 04 17:24:38 raspberrypi test.sh[13035]: hello stderr

確認が終わったら、サービスを止めておきます。

$ sudo systemctl stop test.service

systemd-cat

systemd-cat を実行しても journald にログを記録できるみたいです。
参考にさせてもらったサイト: https://serverfault.com/questions/573946/how-can-i-send-a-message-to-the-systemd-journal-from-the-command-line

$ journalctl | grep bowwow
$ echo 'bowwow' | systemd-cat
$ journalctl | grep bowwow
 4月 04 17:54:27 raspberrypi cat[13153]: bowwow

「Linuxのしくみ」を読んだメモ

試して理解 Linuxのしくみ 実験と図解で学ぶOSとハードウェアの基礎知識 第3刷
を読み終えました。自分用に整理したメモを残しておきます。
勉強用リポジトリ: https://github.com/hiroygo/linux-in-practice

調査用のコマンドやファイル

/proc/cpuinfo ファイル

cat /proc/cpuinfo で CPU 情報を取得できる。

taskset コマンド

プロセスが動作する論理 CPU を指定できる。

$ taskset -c 0,4 ./fuga

time コマンド

プロセスの処理時間を計測できる。
real が経過時間、user がユーザモードの時間、
sys がカーネルシステムコールを実行していた時間。

$ time ps
    PID TTY          TIME CMD
  41058 pts/0    00:00:00 bash
  41067 pts/0    00:00:00 ps

real    0m0.039s
user    0m0.005s
sys 0m0.006s

strace コマンド

プロセスのシステムコールを確認できる。
strace の出力は 1 行が 1 つのシステムコールに対応する。

free コマンド

メモリ状況を確認できる。開放可能なカーネルメモリ領域のサイズと free の合計が available になる。 開放可能なカーネルメモリ領域とはバッファキャッシュやページキャッシュなど。 Swap 行がスワップ領域の情報。

$ free
              total        used        free      shared  buff/cache   available
Mem:        7820180     1660916     3412396      387088     2746868     5450456
Swap:       2097148           0     2097148

ps コマンド

min_flt と maj_flt でページフォルトの総数が取れる。

$ ps -o pid,comm,min_flt,maj_flt
    PID COMMAND          MINFL  MAJFL
   5617 bash              1524      0
   8884 ps                 178      0

readelf コマンド

ELF フォーマットの実行ファイルの情報を表示する。以下のサンプルでは、メモリマップ開始アドレスは 0000000000002580000000000000a000 になっている。

$ readelf -S /bin/sleep
There are 30 section headers, starting at offset 0x91d8:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
...
  [16] .text             PROGBITS         0000000000002580  00002580
       0000000000003692  0000000000000000  AX       0     0     16
...
  [26] .data             PROGBITS         000000000000a000  00009000
       0000000000000080  0000000000000000  WA       0     0     32
...

CPU のカーネルモードとユーザモード(第1章, 第2章)

プロセスから直接デバイスやプロセス管理システムなどにアクセスできないようにするため、 CPU にはカーネルモードとユーザモードがある。カーネルモードの時だけ、 デバイスやプロセス管理システムなどにアクセスできる。プロセスがシステムコールを発行すると、 CPU で割り込みというイベントが発生し、CPU はユーザモードからカーネルモードに遷移する。 システムコール処理が終わると CPU はユーザモードに戻る。 デバイスドライバカーネルモードで動作する。

システムコール(第2章)

システムコールアーキテクチャ依存のアセンブラコードで呼ぶ必要がある。 (特定のレジスタに実行したいシステムコールの番号を設定するなど) C だとインラインアセンブラという機能がある。これだと手間がかかるので、 OS にはシステムコールを呼び出すラッパー関数が用意されている。 CPU がカーネルモードに遷移すると、システムコールの番号から システムコール・テーブルを参照して、対応するカーネルの処理を呼び出す。

プロセスの状態(第4章)

プロセスの状態には実行中、実行待ち(CPU で実行されるの待ち)、スリープ(イベント待ち)、ゾンビがある。

OOM Killer(第5章)

どうやってもメモリが不足するような場合、Out of Memory(OOM) という状態になる。 この場合、OOM Killer が実行され、適当なプロセスが kill されることがある。

仮想記憶(第5章)

プロセスは仮想記憶というメモリを使う。 仮想記憶のアドレスと物理メモリのアドレスの対応はカーネルメモリ領域のページテーブルで ページという単位で管理される。 仮想記憶を使うことで以下を解決する。

  • メモリ断片化
    • 物理メモリでは断片化しているメモリ領域を、あたかも連続しているように見せることが出来る。
  • 他のプロセスやカーネルメモリ領域へのアクセス制御
    • 仮想記憶はプロセスごとに作られ、ページテーブルで管理される。このため他のメモリ領域が見えなくなる。
  • 他のプロセスとのアドレス衝突
    • プロセスを起動するために、ディスクなどからメモリにプロセスをマッピングする時に、他のプロセスとメモリマップ開始アドレスなどが衝突する可能性がある。仮想記憶はプロセスごとに作られるので回避できる。
    • メモリマップ開始アドレスなどは readelf から確認できる。

不正な仮想アドレスにプロセスがアクセスすると、ページフォルトという割り込みが発生し、カーネルページフォルトハンドラが実行され、 カーネルはプロセスに SIGSEGV を通知する。

mmapmalloc(第5章)

mmap ではページ単位でメモリを確保する。malloc でバイト単位で確保できるのは、glibc が中間管理してくれているから。

デマンドページング(第5章)

プロセスが mmap でメモリ確保した段階では、実際に物理メモリが割り当てられているわけではない。 確保した仮想アドレス(ページ)に最初にアクセスした時に、ページフォルトが走り、物理メモリが割り当てされる。

コピーオンライト(第5章)

fork で子プロセスを作成した段階では子プロセス用のメモリ領域は確保されていない。ページテーブルだけコピーされる。 この段階では親プロセスと子プロセスはメモリ領域を共有している。 親プロセスまたは子プロセスのどちらかがメモリに書き込むと、対応するページだけページフォルトが走り、 親プロセス用のページと子プロセス用のページに分かれる。

スワップ(第5章)

ストレージにメモリを退避して、空きメモリを確保する仕組み。 スワップイン、スワップアウトが頻繁に繰り返される状態をスラッシングという。

ページキャッシュ(第6章)

ストレージ上のデータをメモリ上にキャッシュする仕組み。プロセスがデータを変更するとページキャッシュも変更され、後でストレージ上のデータも更新される。 メモリが不足してくると、カーネルはページキャッシュを開放する。 ページキャッシュは複数のプロセスからアクセスされる可能性がある。

ファイルシステム(第7章)

ファイルシステムが無いとユーザは自分で、そのデータについて、ストレージデバイス上でのアドレス、サイズを憶えておかないといけない。 ファイルシステムにはデータ本体(ファイル)とメタデータ(データ本体のアドレス、サイズ、名前、作成日時など)がある。 tmpfs はメモリ上のファイルシステムでアクセスが高速、再起動で消去されるというもの。/tmp などに使われる。

ジャーナリング(第7章)

ファイルシステムの不整合を防ぐ仕組み。ファイルシステムのジャーナル領域にアトミックで実行されるべき複数のファイルシステム操作を保存してから、操作を実行する。

キャラクタデバイスとブロックデバイス(第7章)

Ubuntu でパーティションを作成した時のメモ

1GB のディスク /dev/sdb に 256MB のパーティション
新規作成し、システムにマウントします。
パーティション作成には fdisk を使います。
ファイルシステム作成には mkfs を使います。

/dev/sdb の情報

$ sudo fdisk -l /dev/sdb
Disk /dev/sdb: 1 GiB, 1073741824 bytes, 2097152 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xa6fac314

fdisk でパーティションを新規作成する

/dev/sdb1 が新規作成されます。

$ sudo fdisk /dev/sdb

Welcome to fdisk (util-linux 2.31.1).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.


Command (m for help): p
Disk /dev/sdb: 1 GiB, 1073741824 bytes, 2097152 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xa6fac314

Command (m for help): n
Partition type
   p   primary (0 primary, 0 extended, 4 free)
   e   extended (container for logical partitions)
Select (default p): p
Partition number (1-4, default 1): 1
First sector (2048-2097151, default 2048): 2048
Last sector, +sectors or +size{K,M,G,T,P} (2048-2097151, default 2097151): +256M

Created a new partition 1 of type 'Linux' and of size 256 MiB.
Partition #1 contains a ext4 signature.

Do you want to remove the signature? [Y]es/[N]o: Y

The signature will be removed by a write command.

Command (m for help): p
Disk /dev/sdb: 1 GiB, 1073741824 bytes, 2097152 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xa6fac314

Device     Boot Start    End Sectors  Size Id Type
/dev/sdb1        2048 526335  524288  256M 83 Linux

Filesystem/RAID signature on partition 1 will be wiped.

Command (m for help): w
The partition table has been altered.
Calling ioctl() to re-read partition table.
Syncing disks.

mkfs で ext4ファイルシステムを作成する

/dev/sdb1ext4ファイルシステムを作成します。

$ sudo mkfs.ext4 /dev/sdb1
mke2fs 1.44.1 (24-Mar-2018)
Creating filesystem with 262144 1k blocks and 65536 inodes
Filesystem UUID: 6a53a50e-f300-4896-8f70-2c2db61bb01c
Superblock backups stored on blocks: 
        8193, 24577, 40961, 57345, 73729, 204801, 221185

Allocating group tables: done                            
Writing inode tables: done                            
Creating journal (8192 blocks): done
Writing superblocks and filesystem accounting information: done 

システムにマウントする

/dev/sdb1/mnt にマウントします。サイズも大丈夫そうです。

$ sudo mount /dev/sdb1 /mnt
$ df -h
Filesystem                    Size  Used Avail Use% Mounted on
udev                          462M     0  462M   0% /dev
...
/dev/sdb1                     240M  2.1M  222M   1% /mnt

Vagrant から VM にディスクを追加する

環境

$ VBoxManage -v
6.1.18r142142
$ vagrant -v
Vagrant 2.2.14

Vagrantfile

Vagrantfile に設定を記述することで、 VM にディスクを追加します。 ENV["VAGRANT_EXPERIMENTAL"] = "disks"config.vm.disk :disk, name: "backup", size: "1GB"
で設定しています。以下では 1GB のディスクを追加しています。

# -*- mode: ruby -*-
# vi: set ft=ruby :

ENV["VAGRANT_EXPERIMENTAL"] = "disks"

Vagrant.configure("2") do |config|
  config.vm.box = "bento/ubuntu-18.04"
  config.vm.disk :disk, name: "backup", size: "1GB"
end

VM を起動して確認する

experimental な機能みたいなので、以下のような注意書きが出ます。

$ vagrant up
==> vagrant: You have requested to enabled the experimental flag with the following features:
==> vagrant:
==> vagrant: Features:  disks
==> vagrant:
==> vagrant: Please use with caution, as some of the features may not be fully
==> vagrant: functional yet.
...

VM に入って、ディスクを確認してみます。
私の環境だと /dev/sdb でディスクが追加されていました。
容量も 1 GiB となっています。

$ sudo fdisk -l /dev/sdb
Disk /dev/sdb: 1 GiB, 1073741824 bytes, 2097152 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes

参考にさせてもらったサイト

https://www.vagrantup.com/docs/disks/configuration https://www.vagrantup.com/docs/disks/usage https://gist.github.com/simt2/8178ea232c4ebbae3c88cfbd3e6b146d

CentOS7 に Vagrant をインストールしたときのメモ

マシン設定を変更する

  • BIOS から Intel(R) VT-d を有効にした

VirtualBox のインストール

$ sudo yum -y update
$ sudo yum install –y patch gcc kernel-headers kernel-devel make perl wget
$ sudo wget http://download.virtualbox.org/virtualbox/rpm/el/virtualbox.repo -P /etc/yum.repos.d
$ sudo yum -y install VirtualBox-6.1.x86_64
$ sudo systemctl status vboxdrv

Vagrant のインストール

$ sudo yum install https://releases.hashicorp.com/vagrant/2.2.14/vagrant_2.2.14_x86_64.rpm

Vagrant仮想マシンを作る・終了してみる

$ mkdir vagrant-test
$ cd vagrant-test/
$ vagrant init generic/ubuntu2004
$ vagrant up --provider=virtualbox
$ ssh -p 2222 vagrant@localhost
$ vagrant halt