のぴぴのメモ

自分用のLinuxとかの技術メモ

AWS リージョンのコードと名称の一覧を取得する

結論

例えば「”Asia Pacific (Tokyo)", "ap-northeast-1"」という感じに、リージョン名称とリージョンコードの一覧を取得するのは、下記のAWSのEC2ユーザーズガイドの"Regions and Availability Zones"のリージョン一覧表から取得するのが良さそう。(結局手作業ですが)
docs.aws.amazon.com

(補足)aws cliでのリージョン一覧取得

aws cliで、"aws ec2 describe-regions"でもリージョン一覧が取得できますが、リージョンコード(ap-northeast-1とか)と、EC2のエンドポイント (ec2.ap-south-1.amazonaws.comとか)しか取得できず、ap-northeast-1が”Asia Pacific (Tokyo)"というのは自動では取れないんですね。

aws cli取得例1 アジアのリージョンを取得する

aws ec2 describe-regions  --output json --filters 'Name=region-name, Values=ap-*'
{
    "Regions": [
        {
            "Endpoint": "ec2.ap-south-1.amazonaws.com", 
            "RegionName": "ap-south-1"
        }, 
        {
            "Endpoint": "ec2.ap-northeast-2.amazonaws.com", 
            "RegionName": "ap-northeast-2"
        }, 
        {
            "Endpoint": "ec2.ap-northeast-1.amazonaws.com", 
            "RegionName": "ap-northeast-1"
        }, 
        {
            "Endpoint": "ec2.ap-southeast-1.amazonaws.com", 
            "RegionName": "ap-southeast-1"
        }, 
        {
            "Endpoint": "ec2.ap-southeast-2.amazonaws.com", 
            "RegionName": "ap-southeast-2"
        }
    ]
}

取得例2 リージョンコード一覧を抜き出す

aws ec2 describe-regions --output text --query 'Regions[].{Name:RegionName}'
ap-south-1
eu-west-3
eu-west-2
eu-west-1
ap-northeast-2
ap-northeast-1
sa-east-1
ca-central-1
ap-southeast-1
ap-southeast-2
eu-central-1
us-east-1
us-east-2
us-west-1
us-west-2

取得例3 シェルで、抜き出したリージョンコード一覧をREGIONSという配列にセットする

declare -a REGIONS=( $(aws --output text ec2 describe-regions --query 'Regions[].{Name:RegionName}'|sort) )

取得したREGIONS配列を利用する例

for i in ${REGIONS[@]}; do echo "region code = $i";done
region code = ap-northeast-1
region code = ap-northeast-2
region code = ap-south-1
region code = ap-southeast-1
region code = ap-southeast-2
region code = ca-central-1
region code = eu-central-1
region code = eu-west-1
region code = eu-west-2
region code = eu-west-3
region code = sa-east-1
region code = us-east-1
region code = us-east-2
region code = us-west-1
region code = us-west-2

AWSのドキュメントリンク

個人用のメモです。

サービスに関する情報

RHEL7 on EC2にdockerをセットアップする手順

はじめに

RHEL7 on EC2(License Included)でのdockerセットアップ手順を記載します。
Amazon Linux2へのdockerセットアップ手順(下記)との違いは、RHUI*1のEPELレポジトリを有効化してそこからdockerをインストールするところです。
nopipi.hatenablog.com

手順

RHEL7 on EC2へのdockerインストール

基本的には、ECS agentガイドにある手順を参考にRHEL用に一部カスタマイズして進めます。

EPELレポジトリの登録

awsが提供するライセンス込みのRHELインスタンスにdockerをインストールする場合は、RHUI(Red Hat Update Infrastructure)*2のEPEL(Extra Packages for Enterprise Linux)から、dockerをダウンロードします。
EPELはデフォルトでは登録がないので、まずEPELの登録をします。(2020/11/10 RHUIの仕様が変わったようで、リポジトリ名が変わっていたのでそれに合わせてコマンドを変更しました)

sudo yum install yum-utils
sudo yum-config-manager --enable rhel-7-server-rhui-extras-rpms
Dockertインストール

RHEL全体のパッケージアップデートとdockerのインストールを行います。

sudo yum -y update
sudo yum -y install docker 

dockerの起動とOS起動ときの自動起動設定

sudo systemctl start docker
sudo systemctl enable docker

dockerコマンドをec2-userで実行するための設定

ec2-userのセカンダリグループに、dockerrootグループを追加します。dockerのグループが、dockerではなくdockerrootなので注意してください。
あとdockerdのsocketファイルの所有者が"root:root"なので、dockerrootグループがwriteできるよう所有者を"root:dockerroot"に変更します。(もっとスマートな方法があればいいのですが、ちょっと見つからなかったので)

sudo usermod -a -G dockerroot ec2-user
sudo chown root:dockerroot /var/run/docker.sock 

確認

dockerコマンドを実行して確認する。実行して、ClientとServerの両方の情報が表示されればOKです。

$ docker version
Client:
 Version:      18.03.1-ce
 API version:  1.37
 Go version:   go1.9.6
 Git commit:   3dfb8343b139d6342acfd9975d7f1068b5b1c3d3
 Built:        Fri Jun 15 23:15:12 2018
 OS/Arch:      linux/amd64
 Experimental: false
 Orchestrator: swarm

Server:
 Engine:
  Version:      18.03.1-ce
  API version:  1.37 (minimum version 1.12)
  Go version:   go1.9.6
  Git commit:   7390fc6/18.03.1-ce
  Built:        Fri Jun 15 23:17:24 2018
  OS/Arch:      linux/amd64
  Experimental: false

*1:Red Hat Update Infrastructure 2.0

*2:クラウドで提供されているRHEL(License IncludedなRHELのAMIを利用した場合が該当)ようにクラウド上で提供されている、RHELパッケージのレポジトリサービス。詳細は、こちらを参照

EC2(Amazon Linux2)にprivateなdocker registryの作るメモ

1.はじめに

検証用にプライベートなdocker registryをAWS EC2上のAmazon Linux2に作った時のメモです。レジストリを立てて、別のサーバからdockerイメージをpush/pullして確認するまでを記録しています。
内容は、こちらの記事を参考にしています。

2.セットアップ手順

2.1 前提環境

雑な絵ですが、こんな感じの構成を作ります。
f:id:nopipi:20180721192454p:plain
このメモの前提となる構成は以下の通りです。

  • OS:Amazon Linux2 (AMI ID: amzn2-ami-hvm-2.0.20180622.1-x86_64-gp2)
  • docker registryを動かすインスタンスのNW構成:
    • プライベートDNS: ip-10-203-64-60.ec2.internal
    • パブリックIPを付与またはNATGWでインターネットアクセスを可能にする
    • セキュリティグループで、tcp:443ポート(https)、tcp:22ポート(ssh作業用)の通信ができるよう設定する
  • key pairs

2.2 dockerのセットアップ

プライベートdocker registryをインストールするインスタンスsshログインして、下記の記事の手順でdockerをセットアップします。
nopipi.hatenablog.com

dockerレジストリ用のディレクトリ作成

レジストリ用に以下の構造のディレクトリを作成します。

/home/docker/
         └── registry
             ├── certs
             └── data

ディレクトリの作成と確認手順はこちらですl

sudo mkdir -p /home/docker/registry/{certs,data}
find /home/docker/

2.3 SSLの自己証明書(オレオレ証明書)の作成

検証用なのでオレオレ証明書で勘弁してもらいます。opensslコマンドの途中で聞かれる内容は、とりあえず全てデフォルト(リターン)とします。

openssl req -newkey rsa:4096 -nodes -sha256 -keyout domain.key -x509 -days 365 -out domain.crt
  • オプション説明
    • req : 証明書署名要求(CSR)の管理
      • "-newkey rsa:4096": 4096bitのRSA形式の秘密鍵を同時に作成する
      • "-nodes" : 秘密鍵を暗号化しない
      • "-sha256" :
      • "-keyout domain.key" : 秘密鍵のファイル名を、"domain.key"にする
      • "-x509" : x509の証明書を出力する
      • "-days 365" : 証明書の有効期間を365日にする。
      • "-out domain.crt”: 証明書のファイル名を、"domain.crt"にする。

作成した秘密鍵(domain.key)と、サーバ証明書(domain.crt)をレジストリディレクトリに移動します。

sudo mv domain.key /home/docker/registry/certs/
sudo mv domain.crt /home/docker/registry/certs/

2.4 dockerレジストリの起動

レジストリのイメージ登録とコンテナの実行を同時に行います。

cd /home/docker/registry
docker run -d \
 --restart=always \
 --name docregistry \
 -v `pwd`/certs:/certs \
 -v `pwd`/data:/var/lib/registry \
 -e REGISTRY_HTTP_ADDR=0.0.0.0:443 \
 -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt \
 -e REGISTRY_HTTP_TLS_KEY=/certs/domain.key \
 -e REGISTRY_STORAGE_DELETE_ENABLED=True \
 -p 443:443 \
 registry:2
  • オプション説明
    • "-d": コンテナのバックグラウンド実行
    • "--restart=always" : 再起動ポリシー設定。コンテナダウン時は常にコンテナ再起動を試みる。
    • "--name" : コンテナ名称
    • "-v xxx" : コンテナのVolume機能の設定
    • "-e xxx" : コンテナ内の環境変数設定
    • "-p 443:443" : コンテナの公開ポート設定。コンテナの443portを外部の443portで見せる
    • "registry:2" : レジストリのimage。docker runの中で、docker hubからpullされる

dockerイメージを確認します。

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
registry            2                   b2b03e9146e1        2 weeks ago         33.3MB

registryコンテナが実行されているか確認します。

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                            NAMES
ddd75d47e43a        registry:2          "/entrypoint.sh /etc…"   16 seconds ago      Up 16 seconds       0.0.0.0:443->443/tcp, 5000/tcp   docregistry

3.クライアントからdockerイメージをpushしてみる

dockerのクライアントとなる別のサーバから、プライベートdocker registryへのimageのpush、pullを確認します。

3.1 httpsの接続確認

まずは素のhttpsで、クライアントから、プライベートdocker registryにアクセス可能かを確認します。

(1)サーバ証明書のコピー

プライベートdocker registry用に作成したサーバ証明書を、クライアントにコピーします。ssh ログインするときに"-A"オプションを利用するとsshの鍵を自動でforwardingしてくれるので、クライアントのインスタンス秘密鍵を置く必要がありません。(今回の話からは脱線するので、詳しくはこちらを参照)
MACの場合でデフォルトの秘密鍵を利用する場合は、こんな感じでログインします。

ssh -A ec2-user@[クライアントのDNS or IP]
scp ec2-user@ip-10-203-64-60.ec2.internal:/home/docker/registry/certs/domain.crt .
(2)https接続確認

curl でコピーしたサーバ証明書を指定して、プライベートdocker registryにアクセスしてエラーにならなければOKです。

$ curl --cacert domain.crt https://ip-10-203-64-60.ec2.internal/v2/_catalog

3.2 dockerへのサーバ証明書登録

コピーしたサーバ証明書をdockerの設定ディレクトリに格納します。
格納は、下記ルールに基づいて格納します。

この/etc/dockerディレクトリは、root権限がないと移動することもできないので、rootにチェンジして作業します。

$ sudo -i
# cd /etc/docker/
# mkdir certs.d
# cd certs.d/
# mkdir ip-10-203-64-60.ec2.internal
# cp /home/ec2-user/domain.crt /etc/docker/certs.d/ip-10-203-64-60.ec2.internal/ca.crt
# exit
$ 

サーバ証明書の登録方法は他にもあります。詳細は、公式ドキュメントまたはこちらのQiitaを参照してください。

3.3 プライベートレジストリへのイメージのpushとpull

動作確認として、docker hubからubuntuのコンテナイメージをpullしてきて、プライベートdockerレジストリに登録してみます。

(1)イメージのpullとtag作成

ubuntuのイメージをpullしてきて、プライベートdockerレジストリ登録用のtagを作成します。

docker pull ubuntu:16.04
docker tag ubuntu:16.04 ip-10-203-64-60.ec2.internal/my-ubuntu
docker images
REPOSITORY                               TAG                 IMAGE ID            CREATED             SIZE
ubuntu                                   16.04               e13f3d529b1a        4 days ago          115MB
ip-10-203-64-60.ec2.internal/my-ubuntu   latest              e13f3d529b1a        4 days ago          115MB
(2)イメージのpush

プライベートdocker registryにイメージをpushします。

docker push ip-10-203-64-60.ec2.internal/my-ubuntu
The push refers to repository [ip-10-203-64-60.ec2.internal/my-ubuntu]
709bdd00b1a4: Pushed 
07b9c3c04cbd: Pushed 
6eaddaf493f1: Pushed 
a0e188d0e278: Pushed 
711e4cb62f50: Pushed 
latest: digest: sha256:0d06090fff94c0a3640729c7ef7e6b36ad5b41bec600cc2be92739c90d204243 size: 1357

pushしたイメージが登録されているか、プライベートdocker registryのカタログを参照します。

[ec2-user@ip-10-203-128-109 ~]$ curl --cacert domain.crt https://ip-10-203-64-60.ec2.internal/v2/_catalog
{"repositories":["my-ubuntu"]}
(3)イメージのpull

まず確認のため、ローカルにあるdockerイメージを削除します。

$ docker images
REPOSITORY                               TAG                 IMAGE ID            CREATED             SIZE
ubuntu                                   16.04               e13f3d529b1a        4 days ago          115MB
ip-10-203-64-60.ec2.internal/my-ubuntu   latest              e13f3d529b1a        4 days ago          115MB

$ docker rmi e13f3d529b1a e13f3d529b1a

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE

プライベートdocker registryから、イメージをpullして確認します。

$ docker pull ip-10-203-64-60.ec2.internal/my-ubuntu

$ docker images
REPOSITORY                               TAG                 IMAGE ID            CREATED             SIZE
ip-10-203-64-60.ec2.internal/my-ubuntu   latest              e13f3d529b1a        4 days ago          115MB

opensslコマンドのhelpを出す

opensslコマンドのメモです。

1.コマンドのSYNOPSIS

 openssl command [ command_opts ] [ command_args ]

2.コマンドのヘルプ表示方法

2.1 [command]一覧を出す

"help"というコマンドは無いが、”invalid command”エラーでコマンド一覧が表示される。コマンドの説明は"man openssl"で確認する。

openssl help

2.2 各コマンドのオプション

コマンドの後ろに"help"をつけると、"unknown option"となりオプション一覧が表示される。
例えばreqコマンドのオプションの場合は以下

openssl req help

またreqなどのスタンダードコマンドは、"man req"などmanでコマンド名を指定することで詳細なmanを参照できる。

man req
REQ(1)                                                                      OpenSSL                                                                     REQ(1)

NAME
       req - PKCS#10 certificate request and certificate generating utility.

SYNOPSIS
       openssl req [-inform PEM|DER] [-outform PEM|DER] [-in filename] [-passin arg] [-out filename] [-passout arg] [-text] [-pubkey] [-noout] [-verify]
       [-modulus] [-new] [-rand file(s)] [-newkey rsa:bits] [-newkey alg:file] [-nodes] [-key filename] [-keyform PEM|DER] [-keyout filename] [-keygen_engine
       id] [-[digest]] [-config filename] [-multivalue-rdn] [-x509] [-days n] [-set_serial n] [-asn1-kludge] [-no-asn1-kludge] [-newhdr] [-extensions section]
       [-reqexts section] [-utf8] [-nameopt] [-reqopt] [-subject] [-subj arg] [-batch] [-verbose] [-engine id]

DESCRIPTION
       The req command primarily creates and processes certificate requests in PKCS#10 format. It can additionally create self signed certificates for use as
       root CAs for example.
以下略

LinuxブロックデバイスとNFSとネットワークの関係性

会社でディスク(ブロックデバイス)とNFSの違いはという話題になったので、Linux kernelの中のブロクデバイスNFS(ファイルシステム)、ネットワークの関係をざっくりしたポンチ絵に落としてみました。(簡素化するため箸折っていたり、そもそも私の理解が曖昧な部分もあるので、マジマジ見ると色々気になるかもしれないです)

f:id:nopipi:20180712022708p:plain
Linuxカーネルのブロックデバイスファイルシステム、ネットワークの位置付け

  • 凡例
    • 青色:共通インターフェース
      • VFS(Virtual File system): ファイルシステムを抽象化している層
      • blk:正式な名称がわからないですが、ブロックデバイスを抽象化している層
      • socket:ソケット通信(networkは、ソケット通信で抽象化されている)
    • 緑色:ファイルシステム
    • 黄色:各プロトコル、またはハードに依存しない上位のデバイス
    • 橙色:ハードウェアデバイス、またはハードウェア

Amazon Linux2にdockerをセットアップする手順

Amazon Linux2上にdocker環境を作った時のメモです。

手順

インスタンスを立ち上げて、sshログインしてからの手順です。

(1)dockerのインストール

sudo yum -y update
sudo yum -y install docker

(2)dockerサービスの起動

最初の"start"でdockerサービスを開始(dockerデーモン起動)します。次のenableでOS起動時のサービス開始を有効化します。

sudo systemctl start docker.service
sudo systemctl enable docker.service

(3)ec2-userユーザへの権限付与

dockerコマンドでの操作を行うには、dockerグループに所属している必要があります。usermodコマンドで、ec2-userのセカンダリグループにdockerグループを追加します。

sudo usermod -a -G docker ec2-user

ログイン中のセッションには設定が反映されないため、一度ログアウト&ログインをおこない、idコマンドで設定を確認します。

ログアウト&ログイン後に、
$ id
uid=1000(ec2-user) gid=1000(ec2-user) groups=1000(ec2-user),4(adm),10(wheel),190(systemd-journal),994(docker)

(4)確認

dockerコマンドを実行して確認する。実行して、ClientとServerの両方の情報が表示されればOKです。

$ docker version
Client:
 Version:      18.03.1-ce
 API version:  1.37
 Go version:   go1.9.6
 Git commit:   3dfb8343b139d6342acfd9975d7f1068b5b1c3d3
 Built:        Fri Jun 15 23:15:12 2018
 OS/Arch:      linux/amd64
 Experimental: false
 Orchestrator: swarm

Server:
 Engine:
  Version:      18.03.1-ce
  API version:  1.37 (minimum version 1.12)
  Go version:   go1.9.6
  Git commit:   7390fc6/18.03.1-ce
  Built:        Fri Jun 15 23:17:24 2018
  OS/Arch:      linux/amd64
  Experimental: false
  • Server側のエラー
    • "Cannot connect to the Docker daemon...”: dockerデーモンが起動していない可能性。(2)とNW設定を見直し
    • "Got permission denied while trying to connect ":権限がない可能性。(3)を見直し

EC2でnetconsoleを使ってカーネルメッセージを取得する

1.はじめに

EC2インスタンスで、netconsoleを使ってカーネルメッセージを別サーバに飛ばして見る方法のメモです。ncatを利用しnetconsoleサーバを一時的に作る方法と、syslogサーバに飛ばす方法の、2つを説明します。*1

2.前提

  • OS: Amazon Linux 2 LTS Candidate 2*2
  • 同一VPC内にnetconsoleのサーバとクライアントのインスタンスを立ててカーネルメッセージを取得
  • netconsoleのサーバを一時的に立てて取得する方法とsyslogに飛ばす方法の両方を試します。

3.netconsole serverを一時的に立てて取得

デバックや再現テストとかで一時的に使いたいという場合はncatコマンドで簡易的なサーバを立ててカーネルメッセージを取得することができます。以下の説明の構成概要は以下の通りです。

3.1 netconsole serverの準備

(1) security group設定

今回の例では、UDPの6666port で受診しますので、以下のようなInbound許可ルールを持つセキュリティーグループを設定して、netconsole serverインスタンスにアタッチします。

方向 タイプ プロトコル ポート ソース
Inbound custom UDPルール UDP 6666 172.31.37.0/24
(2) ncatのパッケージインストール

ncatコマンドは、nmap-ncatパッケージに含まれていますので、このパッケージをインストールします。

sudo yum -y install nmap-ncat
(3) ifconfigでネット設定を確認

ncatコマンドでlistenするIPアドレスと、そのIPアドレスが割り当てられているMACアドレス(クライアントで利用)を控えます。

$ ifconfig 
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 9001
        inet 172.31.37.224  netmask 255.255.240.0  broadcast 172.31.47.255
        inet6 fe80::410:9aff:feda:c3d2  prefixlen 64  scopeid 0x20<link>
        ether 06:10:9a:da:c3:d2  txqueuelen 1000  (Ethernet)
        RX packets 27150  bytes 35693608 (34.0 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 10838  bytes 805351 (786.4 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

今回の場合での必要情報は、以下になります。

(4)サーバ起動(フォアグランド実行)

事前にncatコマンドを実行して、netconsoleのパケットの受診ができるようにします。

ncat -l -u 172.31.37.224 6666 | tee ~/netconsole.log
  • オプションの説明
    • "-l" listenモードにするオプション
    • "-u" UDP通信を指定(デフォルトはTCP通信)

3.2 netconsole client実行

カーネルメッセージを出力するサーバの設定です。netconsoleのカーネルモジュールにオプションをつけてローディングすればOKです。一時的な利用を想定して、netconsoleモジュールを手動でロードする手順にしています。

(1)クライアントのネット設定を確認

利用するIPアドレスと、そのIPアドレスが割り当てられているデバイス名を確認します。

eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 9001
        inet 172.31.37.12  netmask 255.255.240.0  broadcast 172.31.47.255
        inet6 fe80::4ca:cdff:fe50:cc8  prefixlen 64  scopeid 0x20<link>
        ether 06:ca:cd:50:0c:c8  txqueuelen 1000  (Ethernet)
        RX packets 26294  bytes 35430311 (33.7 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 2764  bytes 318056 (310.6 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

確認する情報は以下になります。

(2)netconsoleのローディング

modprobeコマンドを利用してnetconsoleカーネルモジュールをローディングします。"netconsole="以下はこのカーネルモジュールのオプション設定です。

sudo modprobe netconsole 'netconsole=6666@172.31.37.12/eth0,6666@172.31.37.224/06:10:9a:da:c3:d2'

netconsoleオプションの書式は以下の通りです。*3
netconsole=[src-port]@[src-ip]/[<dev>],[tgt-port]@<tgt-ip>/[tgt-macaddr]

  • クライアント側 : [src-port]@[src-ip]/[<dev>]
    • src-port : クライアント側で利用するポート
    • src-ip: クライアント側のIPアドレス
    • <dev>: 上記src-ipが割り当てられているデバイス
  • サーバ側
    • tgt-port: サーバ側でListenしているポート
    • tgt-ip: ターゲット側でListenしているIPアドレス
    • tgt-macaddr : 上記tgt-ipが割り当てられているデバイスMACアドレス

4.syslog serverでカーネルメッセージを取得する

カーネルメッセージを定常的またはある程度の期間取得し続ける場合は、syslogサーバを立てて取得した方が良いかもしれません。ここではsyslogをUDPの514ポートでlistenする構成の手順を記載します。

4.1 rsyslogサーバの設定

(1)rsyslogにUDPでのListenを有効化

UDPの514番ポートでの受信ができるようにrsyslog.confを設定します。
/etc/rsyslog.conf:

# Provides UDP syslog reception
$ModLoad imudp
$UDPServerRun 514
(2)設定の有効化

rsyslogを再起動し、設定を有効化します。

$ sudo systemctl restart rsyslog
(3)設定確認

UDPの514番ポートでrsyslogがListenしているかnetstatで確認します。"0 0.0.0.0:syslog"(IPv4)、"0 [::]:syslog "(IPv6)が表示されればOKです。

$ netstat -l | grep syslog
udp        0      0 0.0.0.0:syslog          0.0.0.0:*
udp6       0      0 [::]:syslog             [::]:*

4.2 クライアント(netconsole)の設定

クライアント側は恒常的なメッセージ収集のため、サーバ起動時にnetconsoleが設定されるように"/etc/sysconfig/netconsole"に送信先となるsyslogサーバのIPアドレス設定を行います。

(1)送信先のsyslogサーバIPとポート設定

/etc/sysconfig/netconsole

# The IP address of the remote syslog server to send messages to
SYSLOGADDR=172.31.37.224

# The listening port of the remote syslog daemon
SYSLOGPORT=514
(2)netconsoleのサービスの再起動

netconsole.serviceを再起動し設定を反映させます。
またOS再起動後も反映されるようい"systemctl enable・・・"でサービスを有効化します。

$ sudo systemctl restart netconsole.service
$ sudo systemctl enable netconsole.service

*1:ちなみに、なんでこんな調査をしたかというと、m4インスタンスlinuxでのテストで、"sysctl -w kernel.panic=0"(パニック発生時にハングする)を設定してハングさせたいという話があったのですが、panic発動させると設定に従わずとどうしても再起動してOSがあがってくる (panic=0が機能しない)という話があり調べたものです。その話はまた別途書きたいと思います。

*2:未検証ですが、Amazon Linux1でも基本一緒だと思います

*3:カーネルソースコードのDocument/netにあるnetconsole.txtに説明があります

Linuxのテスト用に、ハングアップやパニック状態にするカーネルモジュールを作ってみた

テストのために、AWSAmazon Linuxインスタンスカーネルレベルでハングアップさせたかったので、テスト用のカーネルモジュールを作ってみました。

できること

  • カーネルのハングアップ(ping応答もできないレベル。プリエンプションをdisableにして実現)
  • 中途半端なハングアップ(ping応答は帰るレベル。リエンプションはenable)
  • カーネルパニック(echo c > /proc/sysrq-triggerと同じ)

使い方

カーネルモジュールのビルド

コードはGitHubにあります。
github.com
(1) 必要なパッケージのインストール
カーネルモジュールのビルドに、gccとkernel-develが必要になります。(Amazon Linuxの場合)

sudo yum -y install git gcc kernel-devel

(2) ソースコードをローカルに持ってきます

git clone https://github.com/Noppy/HangAndPanicKernelModule.git
cd HangAndPanicKernelModule/

(3)ビルドします

make
ls *.ko
hang_panic.ko

使い方

(1)モジュールのインストール

sudo insmod hang_panic.ko
lsmod |grep hang_panic

(2)使い方の確認方法(要root権限)

cat /proc/hang_panic 
<<Hang&Panic module>>
'echo c > /proc/hang_panic' >>> panic
'echo h > /proc/hang_panic' >>> hang(disable local irq and preempt)
'echo H > /proc/hang_panic' >>> hang(disable only local irq)

(3)ハングアップさせる(要root権限)
(a) Local IRQ と preemptionをdisableにしてハング(おそらく、ping応答もできない)

echo h > /proc/hang_panic

(b) Local IRQのみdisable(ping応答はできるレベル)

echo H > /proc/hang_panic

(4)パニック(要root権限)

# echo c > /proc/hang_panic

#蛇足
10年前のkernel2.6時代は、ジャイアントロック(BKL:Big Kernel Lockともいう)があったので、もっと綺麗にハングさせることができたのですが、ソースコードの中を探したのですが、今はシステムワイドなロックのコードがないんですね・・。RHEL3時代に、お客さん先のシステムでBKLに苦しめられたのも、今は昔ということなんですね。

AWS CLIのセットアップ(RHEL/Mac)

1.Amazon LinuxAWS CLIをインストール

Amazon Linuxはデフォルトで、awsコマンドがインストールされています。公式ドキュメントはこちらです。

2.RHELAWS CLIをインストール

こちらを参照

上記の公式ドキュメントの内容をベースにインストールします。

(1) unzipをインストールする

$ sudo yum -y install unzip

(2) AWS CLIバンドルインストーラをダウンロードする

$ curl "https://s3.amazonaws.com/aws-cli/awscli-bundle.zip" -o "awscli-bundle.zip"

(3) ダウンロードしたファイルを解凍する

$ unzip awscli-bundle.zip

(4) インストーラを実行する

$ sudo ./awscli-bundle/install -i /usr/local/aws -b /usr/local/bin/aws

(5) AWS CLIの動作確認

$ aws --version
aws-cli/1.16.77 Python/2.7.14 Linux/4.14.72-73.55.amzn2.x86_64 botocore/1.12.67

3.MacにAWSCLIをインストール

こちらの記事を参照して下さい。

公式のマックセットアップ手順は、こちらを参照

4.AWS CLIの設定

4.1 管理用のPCからAWS CLIを利用する場合(アクセスキーによる認証)

設定手順は、こちらの公式ページを参照して下さい。
アクセスキーの漏洩によるアカウント乗っ取りの可能があるので、EC2インスタンス上でAWS CLIを利用する場合は、次のIAMロールをEC2に割り当てる方法の利用を検討します。*1
(1)IAMユーザのアクセスキー ID とシークレットアクセスキーの生成と取得
以下は公式ドキュメントからの抜粋です。

  • IAM コンソールを開きます。
  • コンソールのナビゲーションペインで、[Users] を選択します。
  • IAM のユーザー名 (チェックボックスではありません) を選択します。
  • [Security credentials] タブを選択し、次に [Create access key] を選択します。
  • 新しいアクセスキーを表示するには、[Show] を選択します。認証情報は以下のようになります。

(2)AWS CLI設定
aws cliに、取得したアクセスキーとシークレットアクセスキー、デフォルトリージョンを設定します。

$ aws configure
AWS Access Key ID [None]: XXXXXXXXXXXXX      <==アクセスキーを設定
AWS Secret Access Key [None]: YYYYYYYYYYYY  <==シークレットアクセスキーを設定
Default region name [None]: ap-northeast-1  <==デフォルトリージョン設定( ap-northeast-1は東京リージョン)
Default output format [None]:

4.2 EC2からAWS CLIを利用する場合(EC2にIAMロール割り当て)

ユーザガイドは私にはわかりずらかったです。こちらの公式ブログの記事の方がわかりやすかったです。
(1)IAM ロールの作成

  • IAM コンソールを開きます。
  • コンソールのナビゲーションペインで、[Roles] を選択します。
  • [Create role]を選択します。
  • [AWS Service]を選び、許可したいサービスを選び[Next Permission]で進む。
  • 適切なpolicyを選び[next:Review]で進む。
  • Roleの名前と詳細説明を入力し、[Create Role]でロールを作成する

(2)EC2へのRole割り当て

  • EC2コンソールを開きます。
  • コンソールのナビゲーションペインで、[Instances] を選択します。
  • [Create Instance]インスタンス作成中に、作成したRoleを割り当てるか、
  • 割り当てたいインスタン(起動中も可)を選択し、[Attach/Replace IAM Role]で作成したロールをEC2に適用

(3)AWS CLI設定
ロールを割り当てる場合は、アクセスキーとシークレットキーは設定しないようにします。

$ aws configure
AWS Access Key ID [None]: 
AWS Secret Access Key [None]: 
Default region name [None]: ap-northeast-1  <==デフォルトリージョン設定( ap-northeast-1は東京リージョン)
Default output format [None]:

5.その他

5.1 コマンド保管

awsコマンドを[TAB]キーで保管してくれるAWSコンプリータという機能があります。これをシェルに組み込めばコマンドを保管してくれます(pythonだからかわかりませんが、保管までに微妙な間があり使いづらいですが)。公式ドキュメントはこちらです。
(1)AWSコンプリータの場所を確認する

$ which aws_completer
/usr/bin/aws_completer

(2)プロファイルに組み込む
環境に依存しますが、例えば "~/.bash_profile"の末尾に下記行を追加します。

#Enable aws command autocompletion
complete -C '/usr/bin/aws_completer' aws

(3)反映
プロファイルを読み込み設定を反映させます。

source .bash_profile

5.2 AWS CLIの設定ファイル

コマンドの設定ファイルは、ホームディレクトリの.awsというディレクトリ(~/.aws)に格納されます。

  • ~/.awsディレクト
    • config    デフォルトリージョンなどの設定
    • credentials アクセスキーとシークレットアクセスキー

MacでVisual Studio CodeをインストールしGit連携するまでの手順

1.はじめに

職場の人にコード何で書いてるか聞いたら、Visual Studio Code(以下、VS Code)を使っているという話しだったので、Macにセットアップしました。かつgit連携もしたのでメモっておきます。

2.VS Codeのセットアップ

2.1 VS Codeのインストール

MicrosoftのページのMacセットアップ手順に従ってセットアップします。→こちらのページ

  • Download Visual Studio CodeからMac用のイメージをダウンロードします
  • ダウンロードしたファイルをアプリケーションフォルダに移動します

f:id:nopipi:20180421160818p:plain:w500

  • 「アプリケーション」からVC Codeを起動します
  • 必要に応じ、Dockに固定します
    • 起動時のアイコンを2本指でクリック(副ボタン)して
    • 「オプション」→「Dock追加」
  • VC Codeを起動します

2.2 コマンドラインからのVS Code設定

コマンドラインから、”code”でVS Codeが起動するように設定を追加します。

  • VC Code上で「Command Palette (⇧⌘P) 」を実行する
  • "shell command: "と入れて絞られた中から、"shell command: Install 'code' command in PATH"を選択して実行(下図参照)

f:id:nopipi:20180421162931p:plain
ちなみに、これを実行すると、"/usr/local/bin/"にcodeという名前で、”/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code”へのシンボリックリンクができます。実行タイトルや公式サイトのセットアップ手順には以下のように$PATH環境変数VS Codeディレクトリが追加されるような記載がありますが、私はこの方法ではできませんでした。*1

Restart the terminal for the new $PATH value to take effect. You'll be able to type 'code .' in any folder to start editing files in that folder.

3.git連携

3.1 gitのセットアップ

VS Codeとgit連携の前に、gitが入っていない場合はまずgitをセットアップします。

  • gitのインストール手順は、googleで探してください。
  • gitの初期設定は、こちらの記事を参照して下さい。

3.2 git連携設定

  • ターミナルを起動し、gitコマンドのpathを控える
$ which git
/usr/local/bin/git
  • VC Codeで「設定」を開き"git.path"に、gitコマンドのパスを設定する

f:id:nopipi:20180421165608p:plain

{
    "git.path" : "/usr/local/bin/git"
}

4.gitを使ってみる

4.1 gitリポジトリをcloneする

ターミナルから普通に操作しても構わないですが、VC Code内で実行したい場合は「統合ターミナル」を起動し操作します。

  • メニューから開く: 「表示」→「統合ターミナル」
  • ショートカット: 「⌃⇧@」(Control + Shift + @)

クローンの方法は、各自調べて下さい。

4.2 VC Codeでgitリポジトリを開いて操作する

VC Code上でのgit利用方法の詳細は、Visual Studio Code で Git を 使う | 験なきものを思はずは を参考にして下さい。
ちなみに私は、VC CodeからAWS CodeCommit上のリポジトリをcloneして利用しています。AWS CodeCommitの使い方については、下記記事を参照して下さい。
nopipi.hatenablog.com

*1:VS Codeのどこかのバージョンで仕様が変わったのかもしれないですね

AWS CodeCommitを使ってみた

1.AWS CodeCommitとは?

AWS CodeCommit は、プライベート Git リポジトリをホストする、安全で高度にスケーラブルなマネージド型のソース管理サービス」です。平たく言えば、「簡単にプライベートなGitリポジトリが利用できるAWSサービス」です。
料金は、5ユーザまで、ストレージ50GB/月まで、10,000Gitリクエスト/月まで、無料で利用できます。
詳細は下記AWSのCodeCommitの概要を参照して下さい。

2.使ってみる

2.1 リポジトリを作ってみる

  • AWSコンソールにログインし、サービスからCodeCommitを選択する。
  • 下記画面が出るので「今すぐ始める」をクリックする

f:id:nopipi:20180421083159p:plain

  • リポジトリの作成
    • リポジトリ名 ※必須。半角英数字と「. _ -」 のみ。
    • 説明 ※任意。日本語可。
  • Eメール通知の設定
    • AWS SNS(Amazon Simple Notification Service)を利用した、プルリクされたなどのイベントのEメール通知設定
    • 通知設定が不要なら「スキップ」する
  • リポジトリに接続」というのが出るので、その内容に従い次の節で、レポジトリ接続用の IAMアカウントを作成します

2.2 gitアクセス用のIAMアカウントを作ってみる

設定内容
  • git接続方式: httpsssh接続が利用でき、かつ複数の認証方式があります。今回はCodeCommitで最も簡単な方法の「AWS CodeCommit 用に HTTPS Git 認証情報を設定」する方法で行います。 *1
  • IAMユーザ: IAMのベストプラクティス *2では、個々のユーザに権限割り当てを行うのではなくグループに権限を割り当てるのを推奨しています。というのを踏まえて、 git管理専用に以下の設定のグループとユーザを作ります。
    • グループDevAdminGrpを作成し必要なポリシーを付与する
    • ユーザgit_adminを作成し、DevAdminGrpに所属させる
手順
  • AWSコンソールから、IAMサービスに移動する
  • グループ作成
    • ダッシュボードから「グループ」を選択し、「新しいグループの作成」を押して「DevAdminGrp」グループを作成する
    • ポリシーのアタッチで、以下の3つのポリシーをアタッチする
      • CodeCommit接続に必要なポリシー (2つ)
        • IAMSelfManageServiceSpecificCredentials*3
        • IAMReadOnlyAccess*4
      • Gitレポジトリ操作用に必要なポリシー(1つ)
        • AWSCodeCommitFullAccess
  • ユーザー作成
    • ダッシュボードから「ユーザ」を選び「ユーザを追加」を押す
    • ユーザ名に「git_admin」を入れる。「プログラムによるアクセス」は後で削除するので、どちらかを適当に選択する。
    • 先ほど作成した「DevAdminGrp」ユーザを追加する
    • 確認をしてユーザ作成を行う。アクセスキーやパスワードはこの後削除するので控えは不要。
  • ユーザ認証情報の設定
    • 作成した「git_admin」ユーザを選び、「認証情報」のタブをクリックします
    • 「サインイン認証情報」で、「コンソールのパスワード」が「有効」になっている場合は「無効」に変更する
    • 「アクセスキー」が存在する場合、右端の「X」ボタンを押し、アクセスキーを削除する
    • AWS CodeCommit の HTTPS Git 認証情報」で「生成」を押し、gitへのhttps接続用のユーザとパスワードを生成する。(ユーザ名とパスワードを必ず控える!)

2.3 gitコマンドでcloneしてみる

gitの初期設定

PCにgitがインストールされてない場合は、gitをインストールします。
インストール後に、gitの初期設定として、(1)メールアドレス、(2)名前、(3)push時のモードを指定して、最後に設定内容を確認します。

$ git config --global user.email "xxxxxxx@gmail.com"
$ git config --global user.name "xxxxxxxx"
$ git config --global push.default simple

$ git config --global -l
CodeCommitと接続しリポジトリをcloneする
  • AWSコンソールで、CodeCommitの対象レポジトリに移動し「クローンURL」の「https」のURLをコピーする
  • コンソールを開き、下記コマンドでcloneする
    • 実行するとユーザ名とパスワードを聞かれるので、控えた「HTTPS Git 認証情報」を入力する
    • "git branch"はcloneされたことを示すためのコマンドで必須ではない
$ git clone  <https://リポジトリcloneURL>
Cloning into 'リポジトリ名...
Username for 'https://git-codecommit.ap-northeast-1.amazonaws.com': XXXXXXXX <==控えたユーザ名を入力
Password for 'https://XXXXXXXX@git-codecommit.ap-northeast-1.amazonaws.com':  XXXXX <== 控えたパスワードを入力
$ cd <リポジトリ名称>
$ git branch -a
* master
  remotes/origin/HEAD -> origin/master
  remotes/origin/master

3.gitコマンドでの、HTTPS Git認証情報の扱いについて

cloneする時に入力したHTTPS接続ときのユーザ名とパスワードは、gitのcredential.helper機能を使い管理することができます。
管理モードは以下の5つです。*5

  • 管理しない
  • メモリ上に一時キャッシュする: cache
  • テキストファイルで保存する: store
  • (Macのみ)Macのキーチェーンを利用する: osxkeychain
  • (Winのみ)Windowsの管理機能(Windows Credential Store)を利用する: wincred

プラットホームごとの確認はしてないですが、少なくともMacはosxkeychainがデフォルトのようですので、特に意識しなくても大丈夫そうです。(git公式ページのgit-osx-installerからセットアップした場合)

*1:https://docs.aws.amazon.com/ja_jp/codecommit/latest/userguide/setting-up.html

*2:https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/best-practices.html#bp-use-aws-defined-policies

*3:IAMSelfManageServiceSpecificCredentialsは、自分のIAMの認証設定の参照/変更用ポリシーのようです

*4:IAMReadOnlyAccessは、IAM情報の参照用ポリシーのようです

*5:Git - Credential Storage

clock_gettime()で負荷をかけたEC2をNetflix FlameScopeでのぞいてみた

はじめに

ablogさんのこちらの記事=>Netflix のオープンソース可視化ツール FlameScope を使ってみた - ablogの二番煎じですが、FlameScopeを教えてもらったので使ってみました。

FlameScopeとは

Netflixのパフォーマンスエンジニアチームがリリースしたperfで取得した性能情報をブラウザでビジュアルに分析できるツールです。詳しくはNetflixのblogablogの記事を参照してください。

この記事で書いていること

  • MacにFlameScopeをセットアップする時の注意事項
  • AWS EC2のt2.microインスタンス場で、clock_gettime(gettimeofdayを実行した時に叩かれるkernelのAPI)で負荷をかけた時のサンプル

セットアップ手順①(MacにFlameScopeをセットアップする)

基本は、GitHub - Netflix/flamescope: FlameScope is a visualization tool for exploring different time ranges as Flame Graphs.に書いてあります。
基本FrameScopeのgithubに記載されている手順でいけますが、私のMacは前提のツールが何も入っていなかったのでもう一手間かかりました。

(1)前提パッケージのセットアップ

(a)gitコマンドのセットアップ

下のqiita記事を参考にgitコマンドをインストールしてください。

gitはAppleの認証がなくインストールでエラーになる場合がありますが、その場合はエラーになった後に"システム環境設定"->"セキュリティーとプライバシー"からインストールを実行してください。

(b)pipコマンドのセットアップ
  • Xcodeのセットアップ

pipのセットアップの中でccコマンドでビルドしているようなので、 ccコマンドを使えるようにするためにMacの開発環境であるXcodeを先にインストールします。
インストールは、”AppStore”から”Xcode”で検索してインストールします。

  • pipインストール

こちらの記事を参考にpipをインストールします。

手順は、
(i)get-pip.pyダウンロード
https://pip.pypa.io/en/stable/installing/ から、get-pip.pyファイルをダウンロードする。
(ii)pipインストール

python get-pip.py --user
export PATH="$HOME/Library/Python/2.7/bin:$PATH"
echo 'export PATH="$HOME/Library/Python/2.7/bin:$PATH"' >> ~/.bash_profile

(iii)既存パッケージのアップデート

pip freeze --local | grep -v '^\-e' | cut -d = -f 1 | xargs pip install -U --user

(2)FlameScopeのセットアップ

FlameScopeのgithubにあるインストール手順をそのまま実行します。

cd ~/
git clone https://github.com/Netflix/flamescope
cd flamescope
pip install -r requirements.txt

セットアップ手順②(測定対象のLinuxにperfをインストールする)

今回はAWSのEC2(t2.micro)のAmazon Linux 2を利用しています。FlameScopeのgithubの説明通りです。

sudo yum install perf

テスト用にワークロードを準備する

負荷がかかってない状態で測定しても何も面白くないので、何からかの負荷がけをします。負荷は何でもよいのですが、今回は時刻取得を約3355万回(2^25)繰り返す負荷ツールを作りました。clock_gettime()を呼び出す部分をアセンブラで書いていますが、ただの趣味です。こんなキモいことしなくても大丈夫です。

/* work.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#define _GNU_SOURCE
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/types.h>

#define MAX 1LU << 25   /* MAX = 2^25  */

int main() {
    struct timespec tp;
    char *mes;
    int  ret;
    unsigned long loop;
 
    for(loop=0; loop<MAX; loop++){ 
        asm volatile(
            /* call clocl_gettime api */
            "mov %3, %%rsi;"
            "mov %2, %%rdi;"
            "mov %1, %%rax;"
            "syscall;"
            "mov %%eax, %%eax;"
            "mov %%eax, %0;"
            : "=&m"(ret)
            : "g"(SYS_clock_gettime),
              "g"(CLOCK_REALTIME),
              "g"(&tp)
            :
        );
    }
    printf("execute %lu times.\n",loop);
    return ret;
}

このコードをgccでビルドします。(デフォルトではgccが入っていないのでyumでインストールしてビルドします)

sudo yum install gcc
gcc work.c

linuxで測定する

負荷は、作成したツールを4回、10秒のインターバルを置いて実行するようにしました。

sleep 15;for i in `seq 1 4`;do time ./a.out;sleep 10;done

それと同時に、別のターミナルでperfでプロファイルの収集を開始します(秒間49回で、120秒間測定)。

sudo perf record -F 49 -a -g -- sleep 120
  • -F: 49:秒間49回データ収集を行う。(秒間50回が分析にちょうど良い分解能度で、でも50回だと周期性が出てハマってしまう(lock-step)可能性があるから、周期性が出ないよう半端な49回にするということ?)
  • -a: 全てのCPUコアを対象にシステムワイドでプロフィル収集する
  • -g: call-graphを有効化する

perfのデータ収集が完了したらレポートを出力しgzip圧縮する

sudo perf script -f --header > stacks.log
gzip stacks.log

FlameScopeで分析する

  • 収集し、圧縮したstacks.log.gzファイルをMacに転送します。
  • 転送したstacks.log.gzを”flamescope/example”に移動します*1
  • FlameScopeを起動します。
cd ~/flamescope
python run.py
  • ブラウザで、http://127.0.0.1:5000/にアクセスする。
  • stack.log.gzを選択する
  • 右上のRawsから、perfの分解能度に合わせた行数を選択する(49を選択)

すると、こんな感じで見えます。
赤いところがイベントが多数出ている→負荷がかかっている、場所になります。
f:id:nopipi:20180411014615p:plain

FladeScopeでドリルダウン分析してみる

上記の赤い濃淡の図の中から気になる部分があったら、その部分をFlameGraphでドリルダウン分析します。例えば最初の赤い時間帯の開始タイミングと終了タイミングで2回クリックするとその範囲が選択され、下記のFlameGraphで詳細分析することができます。
f:id:nopipi:20180411015940p:plain

t2.microのクロックソース

上記の図を見ると、

  • a.outのmain()から、システムコール呼び出し(entry_SYSCALL_64_after_hwframe)からdo_syscall_64()が呼ばれ、
  • そこから、sys_clock_gettime()が呼ばれて以下実行され、
  • 最終的に、pvclock_clocksource_read()が呼び出されているのがわかります。

なおpvclock_clocksource_read()のコードがどこにあるかカーネルを別途見ると、arch/x86/kernel/pvclock.cにあるようです*2。pvclockという名前からparavirtualのclocksourceであることが推測でき、コードタイトルにも”paravirtual clock -- common code used by kvm/xen”とあるので、t2.microのクロックソースコードはパラバーチャルであることが裏付けられます。( ちなみに、lvm/xenで共通化されているんですね)

処理負荷が高いポイント

clock_gettimeを回して負荷をかけているので当然、処理時間の大半がシステムコール処理にリソースを費やされています。実際FlameGraphを見るとdo_syscall_64が処理時間全体の7割弱を占めているのがわかります。ただ、実際に時刻取得処理を行うdo_clock_gettime()は、do_syscall_64()の半分の処理時間割合であることから、システムコールによるユーザ空間からカーネル空間へのコンテキストスイッチは相当のオーバーヘッドがあることが伺えます。
f:id:nopipi:20180411023148p:plain

(蛇足)/sysからt2.microのクロックソースを確認

蛇足ですが最後に、実機の/sysからカレントのクロックソースを確認します。

kernel的には、xen, tsc, hpet, acpi_pmの4つが選択可能に見えます。

$ cat /sys/devices/system/clocksource/clocksource0/available_clocksource 
xen tsc hpet acpi_pm 
  • (2)カレントのクロックソース

xenですね。

$ cat /sys/devices/system/clocksource/clocksource0/current_clocksource 
xen

subprocessでコマンド実行し、例外処理でOSErrorとコマンドのリターンコードの非ゼロ(エラー)をそれぞれハンドルするサンプル

サンプルコード

私自身の勉強用です。コードは、python2.7ベースです。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
import subprocess

def call_subprocess(cmd):
    try:
        r = subprocess.check_output(
            cmd,
            stderr=subprocess.STDOUT
            )
        sys.stdout.write(r+'\n')
    except OSError as e:
        sys.stderr.write('file ='+str(e.filename)+'\n')
        sys.stderr.write('errno='+str(e.errno)+'\n')
        sys.stderr.write('error='+e.strerror+'\n')
        return(1)
    except subprocess.CalledProcessError as e:
        sys.stderr.write('ret='+str(e.returncode)+'\n')
        sys.stderr.write('cmd='+str(e.cmd)+'\n')
        sys.stderr.write('output='+e.output+'\n')
        return(1)
    return(0)

#test1 a command is success
print('<<<<<test1>>>> a command is success.' )
cmd = [ 'echo','hoge hoge' ]
ret = call_subprocess(cmd)
print('function return = '+str(ret) )

#test2 command is failed
print('<<<<<test2>>>> a command is failed.' )
cmd = [ 'cat','hoge hoge' ]
ret = call_subprocess(cmd)
print('function return = '+str(ret) )

#test3 OS error
print('<<<<<test3>>>> OS Error.' )
cmd = [ 'NonexistingFile','hoge hoge' ]
ret = call_subprocess(cmd)
print('function return = '+str(ret) )

プロセスのVSZ,RSSとfree,meminfoの関係を実機で確認する

1.はじめに

1-1.この記事の要旨

psコマンドのVSZ(仮想メモリ)、RSS(物理メモリ)の挙動について質問を受けたので、簡単な検証プログラムを作ってmalloc/freeのメモリ確保/解放や、データの読み込み・書き込みとVSZ/RSSの関係性及び、freeコマンドとmeminfo情報でシステムワイドなメモリの挙動について確認しました。
検証結果から以下の挙動を実機で確認しました。

  • 検証結果
    1. malloc()の時点では、VSZのみ増加し、RSSやfreeコマンドのusedは増加しない
    2. データreadでは、物理メモリの割り当てが発生しないため、RSSとfreeコマンドのusedは増加しない(COW(Copy-On-Write)実装によるもの)
    3. データwriteして初めて、物理メモリの割り当てが発生し、RSSとfreeコマンドのusedが増加する

f:id:nopipi:20171115024528p:plain
psコマンドのVSZ/RSS挙動
f:id:nopipi:20171115024541p:plain
freeコマンドのused, free,availableの挙動
f:id:nopipi:20171115024535p:plain
meminfoの挙動*1

1-2.(予習)メモリに関する指標とlinuxのメモリ挙動について

この記事に出てくる、メモリに関する指標値の説明です。

  • psコマンド
    • VSZ : プロセスが確保している仮想メモリサイズ
    • RSS : プロセスが確保している物理メモリサイズ
  • freeコマンド
    • used : カーネルや各プロセスが利用しているメモリの総量です。*2
    • buffer /cache: キャッシュやバッファとして利用されいるメモリ量です。(この記事では、単にbufferと記載しています)
    • free : 未使用のメモリ総量です。
  • meminfo
    • MemFree : 未使用のメモリ量です。freeコマンドの"free値"の元になっています。
    • AnonymousPage(無名ページ): swapアウト対象となるページ。ざっくりというとプロセスが普通使っているメモリ。
    • FilePage(ファイルページ):メモリ不足時、解放されるページ。ざっくりいうとファイルキャッシュのメモリ。
    • active: AnonymousPage/FilePageのうち、比較的最近アクセス(read or write)されたページ。swapアウトや解放されないページではない。*3
    • inactive:AnonymousPage/FilePageのうち、しばらくアクセスされていないページ

2.検証環境と検証方法

2-1.検証環境

検証環境は以下の通りです。

  • RHEL7.4(3.10.0-693.el7.x86_64)
  • サーバ構成: CPU 1core、メモリ 2GB(VirtualBox利用)

2-2.検証方法

下記動作を行う検証プログラムを作成し実行します。なお検証をわかりやすくするために、動作の間に10秒のsleepをかけています。

  1. malloc()で512MBのメモリを確保する
  2. 確保した512MBのメモリ領域を20秒かけてreadする(1秒間隔で1/20のサイズずつ20回readする)
  3. 同じ領域に、20秒かけてデータを書き込む
  4. もう一度同じ領域を、20秒かけて読み込む
  5. free()で確保したメモリを解放する

プログラムは以下のリンク先にありますので、これをgccで"gcc verifying_memory.c"とかしてビルドします。
Verifying on Memory behavior · GitHub
ちなみにglibcmalloc()は、サイズが128kb(変更可)より大きい場合はmmap()取得になります。*4
ですので512MB確保している今回の検証は、必ずmmap()になります。

2-3.測定方法

(1)psコマンドによるVSZ,RSS情報の取得

簡単ですが、下記のワンライナーコマンドで1秒間隔で取得しました。(検証プログラムのファイル名は、"a.out")

echo -n 'DATE               '; ps -aux|head -n 1;while true;do printf '%s ' "`env LANG=C date '+%X.%N'`";env LANG=C ps -aux|grep -e a.out|grep -v grep;sleep 1;done
(2)freeコマンドとmeminfo情報の取得

こちらの記事のシェルで取得しました。
freeコマンドとmeminfoを取得してCSV形式で保存するシェルスクリプト - のぴぴのメモ

3.結果

3-1.全体の結果

psコマンドのVSZ/RSS、freeコマンド、meminfoの推移を並べると以下のようになります。

f:id:nopipi:20171115024528p:plain
psコマンドのVSZ/RSS挙動
f:id:nopipi:20171115024541p:plain
freeコマンドのused, free,availableの挙動
f:id:nopipi:20171115024535p:plain
meminfoの挙動

3-2.プロセスのVSZ/RSS挙動

f:id:nopipi:20171115024528p:plain
psコマンドのVSZ/RSS挙動

ポイント① malloc()した時の挙動→VSZのみ増加

malloc()で512MBを取得したタイミングで、VSZが増加しているのがわかると思います。
一方RSSは、malloc()しただけでは増えてなく、このタイミングでは物理メモリの割り当てが発生していないことがわかります。(次のfreeコマンドやmeminfoの結果と照らし合わせて見るとよりわかりやすいです。)

ポイント② 1回目のデータread時→RSSは増えない

malloc()した後、そのデータをreadしても、物理メモリの割り当ては発生しません。そのため、RSSも増加しません。
実はプロセスがカーネルからメモリ確保したタイミングでは、確保した仮想メモリのページは、”zero page”という1ページがすべて0データで埋められた特殊なページにマッピングされており、データをreadした時は、その"zero page"の値を参照するためです。*5

ポイント③ データwrite→RSSが増加する

データのwriteが発生した時点で、RSSが増加していることがわかります。つまりデータへのwriteのタイミングで、そのwriteした仮想メモリのページに、晴れて物理メモリが割り当てられたということです。
これはカーネルのCOW(Copy-On-Write)という手法で、mallocなどでメモリを確保したタイミングでは実際には物理メモリの割り当てはせず、必要になったタイミングで初めて物理メモリを割り当ているからです。*6
もう少し詳しく書くと、先ほど説明したzero pageは書き込み禁止設定がされており、そこにデータを書こうとするとページフォルトが発生します。ページフォルトが発生すると、ページフォルトの割り込み処理の中にあるCOWの実装で、zero pageの内容を新しいページにコピーしして、そのページを改めて仮想ページとマッピングします。その時にRSSが+1ページ加算されます。

3-3.システムワイドな挙動(freeコマンド/meminfo)

f:id:nopipi:20171115024541p:plain
freeコマンドのused, free,availableの挙動
f:id:nopipi:20171115024535p:plain
meminfoの挙動

ポイント① malloc()した時の挙動→usedもAnonymousPageも増えない

malloc()した時には、RSSが増えてない、つまり物理メモリへのマッピングがされていないため、freeコマンドやmeminfoでシステムワイドで見た物理メモリの利用状況でも、変化はありません。

ポイント②1回目のデータread時→変化しない。

同じですね。物理メモリの割り当てが発生しないため、freeコマンドやmeminfoも変化はありません。

ポイント③ データwrite→used上昇、AnonymousPage上昇

Writeするタイミングで、物理メモリの割り当てが発生するため、Usedが上昇します。meminfoでさらに詳しく見るとその上昇しているところが、AnonymousPage(無名ページ)であることが確認できます。

*1:kernelは、”MemTotal - MemFree - AnonymousePage(Active/InActive) - FilePage(Active/InActive) - Unevictable+Mlocked”で算出しています。

*2:"total - free - buffers - cache"で計算された値になります。RHEL6まではbuffer/cacheを含んでいましたが、RHEL7からはbuffer/cacheが含まれない値になりました。

*3:/proc/meminfo の Inactive は利用可能なメモリ領域ではない - ablog

*4:glibcmalloc()は、指定するメモリサイズが小さい場合(閾値MMAP_THRESHOLDで設定されており、デフォルトは128kb)は、Java vmのヒープのように、カーネルから取得済みで未使用となったメモリ領域をglibc内で保持して再利用する実装があるため、malloc()により必ずしもVSZが増加するとは限りません。詳しくはmalloc()のmanの注意(NOTES)を参照。

*5:memory management - Linux kernel: Role of zero page allocation at paging_init time - Stack Overflow

*6:コピーオンライト - Wikipedia