オリジナルのgit-syncでnginxの設定を動的に変更する

概要

運用要件の変更で、コンテナの中のnginx.confを開発チームが触りたいとなったので、構成変更を行った際の備忘録です。

今までは、kubernetesのkostomizeで、configMapGeneratorを利用したconfigの生成を行なっていた為、別の方法でnginx.configを管理するように変更する方法を考える必要がありました。

要件は以下のようになります。

1. nginx.configだけを管理するGitリポジトリを作成
2. サイドカーとしてGitリポジトリを一定間隔でpullするコンテナを起動する
3. 更新があった場合、nginxプロセスをHUPシグナルを送信する

最初は公式のgit-syncを使い検証を行なっていました。

このイメージで上手く行けば良かったのですが、残念ながら
3. 更新があった場合、nginxプロセスをHUPシグナルを送信する
を行う為には各種パッケージを追加する必要があり、
それなら勉強の為に自前で同じ動きをさせてみよう!となりました。

app.py

アプリケーションの動きは、公式のgit-syncに合わせて、
GIT_SYNC_REPO       ・・・ 対象リポジトリ
GIT_SYNC_BRANCH   ・・・ 対象ブランチ
GIT_SYNC_DEST        ・・・ 保存場所
を、環境変数から読み込むようにしました。

GIT_SYNC_DESTは、nginxコンテナからも読み込ませるので、emptyDirをvolumeとしてマウントしたPATHにします。

import os
import time
import git
import json
import subprocess
from datetime import datetime

def main():

  while True:
    dt = datetime.strftime(datetime.now(), '%Y/%m/%d %H:%M:%S')
    log = {}

    if os.path.exists(os.getenv("GIT_SYNC_DEST")):
        repo = git.Repo(os.getenv("GIT_SYNC_DEST"))
        repo.git.checkout(os.getenv("GIT_SYNC_BRANCH"))

        git_result = repo.git.pull()
        log["timestamp"] = dt
        log["message"]   = f'git pull from {os.getenv("GIT_SYNC_DEST")}'
        print(json.dumps(log))

        change_flg = 0
        for log in git_result.splitlines():
            if 'Updating' in log:
                change_flg = 1

        if change_flg == 1:
            log = {}
            dt = datetime.strftime(datetime.now(), '%Y/%m/%d %H:%M:%S')
            cmd = 'pkill -HUP -f "nginx: master process"'
            subprocess.call(cmd, shell=True)
            log["timestamp"] = dt
            log["message"]   = 'exec HUP nginx master process'
            print(json.dumps(log))

    else:
        git_result = git.Repo.clone_from(
            os.getenv("GIT_SYNC_REPO"),
            os.getenv("GIT_SYNC_DEST")
        )
        log["timestamp"] = dt
        log["message"]   = f'git clone {os.getenv("GIT_SYNC_REPO")} to {os.getenv("GIT_SYNC_DEST")}'
        print(json.dumps(log))

    time.sleep(60)

if __name__=="__main__":
    main()

requirements

GitPython

Dockerfile

本来であればgitアクセスの際の利用するSSHの設定は、secretなどを使ってpodsに渡した方が良いですが、今回は割愛しています。

FROM ubuntu:latest

ENV TZ Asia/Tokyo
ENV DEBIAN_FRONTEND=noninteractive

USER root

RUN apt-get -y update && apt-get -y install tzdata procps python3 pip git\
    && apt-get clean && rm -rf /var/lib/apt/lists/*

ENV HOME=/tmp
WORKDIR /tmp

COPY .ssh /root/.ssh
COPY app /app
RUN pip3 install --upgrade pip
RUN pip3 install -r /app/requirements.txt

ENTRYPOINT [ "/bin/python3 /app/app.py" ]

deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  labels:
    app: nginx

spec:
  template:
    spec:
      # サイドカーコンテナからnginxのプロセスを操作出来るようにする
      shareProcessNamespace: true

      containers:
        # nginxコンテナ
        - name: nginx

          # 共有のからディレクトリをマウント
          volumeMounts:
          - name: git-sync-volume
            mountPath: /tmp/git

          env:
          - name: NGINX_CONF
            value: /tmp/git/nginx-config/conf.d

          # nginxの起動前にconf.dディレクトリを/etc/nginx/conf.dに
          # シンボリックリンクを貼ります。
          # sleep を入れているのは、git pullでファイルの同期を待つ為に入れています
          command: ["/bin/sh", "-c", "--"]
          args: ["sleep 5 && ln -sf ${NGINX_CONF} /etc/nginx/conf.d && nginx -g \"daemon off;\""]


        # git-sync コンテナ
        - name: git-sync
          image: git-sync:latest
          imagePullPolicy: Always
          volumeMounts:
          - name: git-sync-volume
            mountPath: /tmp/git

          # 環境変数でリポジトリの情報を渡します
          env:
          - name: GIT_SYNC_REPO
            value: "git@github.com:xxx/nginx-config.git"
          - name: GIT_SYNC_BRANCH
            value: master
          - name: GIT_SYNC_DEST
            value: "/tmp/git/nginx-config"

          # git-syncコンテナから、nginxプロセスのPIDを取得する際に必要
          securityContext:
            capabilities:
              add:
              - SYS_PTRACE

      restartPolicy: Always

      volumes:
        - name: git-sync-volume
          emptyDir: {}

nginx-configリポジトリがmasterブランチが更新された場合、
git-syncコンテナが1分間隔でgit pullを行い
nginxコンテナの親プロセスにHUPを送信します。

これであれば、HUPの前にconfigtestを行い失敗したらSlackで通知を飛ばすとかも容易ですね。

systemdのunitファイルの差分編集方法

systemdのunitファイルの差分編集する方法を初めて知ったのでメモ

ディストリビューションパッケージからインストールしたUnitファイルは、以下を行うことで設定の上書きが出来る

systemctl edit google-fluentd

[Service]
Environment='元の値(存在しなければ空)'
Environment='LOG_ELASTICSEARCH_HOST=dev-log-elastic01'

※そもそもsystemdで環境変数を見てくれないのを忘れていてハマりました

Adventカレンダー 社内Docker環境の話

会社でやっているAdventカレンダー 3日目の記事です。

ここ1年で蓄積したDockerのナレッジを書きます。

弊社では、ローカルの開発環境でDockerを利用しています。

決して安定している開発環境とは言えず、日々ブラッシュアップしている状況ですが、培ったナレッジを書き残します。

dockerネットワークの話

コンテナは起動する事に、排他的な独自のネットワークを作ります。
単純にコンテナを起動するだけであればあまり気にする事はありませんが、IPアドレスによる制限を行っている場合は起動の度にコンテナのIPアドレスが変わる為、ネットワーク設計を考える必要があります。

弊社の環境ではL7のPATHルーティングを必要としますので、以下のような構成になります。

127.0.0.1:80
 ↓
Nginxロードバランサコンテナ
 ↓     ↓
コンテナA  コンテナB

上記の構成の場合、
① NginxロードバランサーコンテナのIPアドレスを固定する。
② X-Forwardedに127.0.0.1を含む場合アクセスを許可する。

の2つの許可方法が考えられますが、①で行うように設定しようと考えました。

docker-composeでコンテナを起動する場合、単一のコンテナだけではなく全てのコンテナにIPアドレスを固定指定する必要がありますが、
上記のようにコンテナが3つだけであれば良いのですが、実際は20ぐらいのコンテナが動いています。

同一ネットワークでNginxロードバランサコンテナだけIPアドレスを固定してみましたが、DHCPレンジとぶつかるケースがコンテナ側の起動が出来ない事がありました。
その為、ネットワークの設定、DHCPプールの設定を行う必要があります。

docker network create --subnet=172.19.0.0/16 --gateway=172.19.0.1 --ip-range=172.19.1.0/24 ネットワーク名

・Dockerネットワーク全体のネットワークは172.19.0.0/16
・ゲートウェイは172.19.0.1
・DHCPレンジは172.19.1.0/24の範囲で設定する。

Dockerネットワーク作成後、172.19.1.0/24 以外を固定IPアドレス設定可能ネットワークとして、通常通りコンテナにIPアドレスを固定しても、
DHCPレンジとのバッティングも発生しなくなりました。

    networks: 
      ネットワーク名:
        ipv4_address: 172.19.0.5

node ビルドの話

nodeのbuild(npm run dev等)がとにかく遅い。
ソースコードをそのままvolumesとしてマウントを行いビルドを行うと、node_modules配下が作られるが、
node_modules配下を”別物として”マウントする事で、ビルドの速度改善が見られた。

services:
  app:
    build:
      context: ./app
    volumes:
      - app/src:/var/www/DocmentRoot
      - node_modules_volume:/var/www/DocmentRoot/node_modules

volumes:
  node_modules_volume:

快適に開発が行える環境までもう一歩。
現在は社内からクレームが多い 「自分の業務とは関係ないコンテナを立ち上げたくない(重い)」の対応を進めているので、
動き始めたらその事も記事に書く予定です。

GitHub Actionsの使い方

Github謹製のCD/CIツールとしてGitHub Actionsを使ってみましたのでメモを残します。

今回テストしたGithubは、
①githubのソースコードを含めたコンテナイメージ化
②コンテナイメージをGCR(Google Container Registry)にアップロード
③GCRよりGKE上のkubenetesにデプロイ
④部分的にチャットワークにメッセージ送信
の4つです。

GCPのcredential.jsonをGithubのSercretに登録する

GCPを触るので、事前に取得した認証ファイルを使うことになりますが、リポジトリ内に認証ファイルを上げずに、githubのsercretに登録します。
jsonファイルの中身をそのまま貼り付けるのではなく、base64エンコード形式で貼る必要があります。

bast64 xxxxx.json #認証ファイル

GithubActionファイルの作成

Actionメニューより、NewWorkFlowを選択します。

以前の情報を見ると、初期の頃はビジュアライズなインターフェースで作成が出来たようですが、今はYAML形式での記載になります。
今回は、Docker imageのテンプレートを選びます。

リポジトリの/.github/workflows/dockerimage.ymlとしてテンプレートが挿入されるので、以下のようにカスタマイズします。

name: Docker Image CI

# CI/CDのトリガーになる動作を指定
# 以下はmasterブランチが変更されたら...
on:
  push:
    branches:
      - master

jobs:
  build:
    # CIで使うベースイメージ?
    runs-on: ubuntu-latest

    steps:
    # ベースイメージにリポジトリをcloneする 
    - uses: actions/checkout@v1

    # チャットワークにgit logの内容を送信する
    - name: Send cahtwork from gitlog
      env:
        ROOM_ID: xxxxxxxx
        API_TOKEN: xxxxxxxxxxxxxxx
        REPO_NAME: 'test repo'
      run: |
        git log --numstat -m -1 --date=iso --pretty='[%ad] %h %an : %s' > gitlog.txt
        curl -s -X POST -H "X-ChatWorkToken: ${API_TOKEN}" -d "body=[info][title]${REPO_NAME}[/title]`cat gitlog.txt`[/info]" "https://api.chatwork.com/v2/rooms/${ROOM_ID}/messages"

    # Docker buildを実行
    - name: Build the Docker image
      run: docker build -f Dockerfile . -t {タグ}  #リポジトリ直下にDockerfileを置いています

    # イメージにタグを付ける
    - name: Tagging Docker
      run: docker tag {タグ} gcr.io/{プロジェクトID}/{タグ}:latest

    # 
    - name: gcloud auth
      uses: actions/gcloud/auth@master
      env:
        GCLOUD_AUTH: ${{ secrets.GCR_KEY }}

    - name: Upload to GCS
      env:
        PROJECT_ID: skyticket-devel-161401
        GCLOUD_AUTH: ${{ secrets.GCR_KEY }}
        GOOGLE_APPLICATION_CREDENTIALS: ./service-account.json
      run: |
        export CLOUD_SDK_REPO="cloud-sdk-$(lsb_release -c -s)"
        echo "deb http://packages.cloud.google.com/apt $CLOUD_SDK_REPO main" | sudo tee -a /etc/apt/sources.list.d/google-cloud-sdk.list
        curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
        sudo apt-get update
        sudo apt-get install google-cloud-sdk
        echo ${GCLOUD_AUTH} | base64 --decode > ${GOOGLE_APPLICATION_CREDENTIALS}
        cat $GOOGLE_APPLICATION_CREDENTIALS | docker login -u _json_key --password-stdin https://gcr.io
        gcloud auth activate-service-account --key-file $GOOGLE_APPLICATION_CREDENTIALS
        gcloud config set project ${PROJECT_ID}
        gcloud docker -- push gcr.io/${PROJECT_ID}/sky_scrayping/sky_scrayping:latest

    # GKEにデプロイ
    - name: Deploy Kubenetes
      env:
        cluster_name: test-cluster
        cluster_ip: xx.xx.xx.xx
      run: |
        kubectl config set-cluster ${cluster_name} --server=http://${cluster_ip}
        kubectl config set-context ${cluster_name}
        gcloud container clusters get-credentials ${cluster_name} --zone=us-west1-a

        # kubenetest のマニフェストはmanifest/dev.ymlに置いており、
        # deployを強制する為にマニフェストの一部を置換しています。
        sed -i -e "s/annotations_reloaded-at/`date  +%s`/g" manifest/dev.yml
        kubectl apply -f manifest/dev.yml

    # チャットワークにデプロイ完了の通知する
    - name: Send cahtwork from Deploy
      env:
        ROOM_ID: xxxxxxxx
        API_TOKEN: xxxxxxxxxxxxxxx
        REPO_NAME: 'test repo'
        BODY: '開発環境のデプロイが完了しました'
      run: |
        curl -s -X POST -H "X-ChatWorkToken: ${API_TOKEN}" -d "body=[info][title]${REPO_NAME}[/title]`echo -e ${BODY}`[/info]" "https://api.chatwork.com/v2/rooms/${ROOM_ID}/messages"

    # Actionが失敗した時はチャットワークにデプロイ失敗の通知する
    - name: error notification
      if: failure()   # これ重要
      env:
        ROOM_ID: xxxxxxxx
        API_TOKEN: xxxxxxxxxxxxxxx
        REPO_NAME: 'test repo'
        BODY: '開発環境のデプロイが失敗しました\n詳細はgithubActionのステータスを参照してください'
      run: |
        curl -s -X POST -H "X-ChatWorkToken: ${API_TOKEN}" -d "body=[info][title]${REPO_NAME}[/title]`echo -e ${BODY}`[/info]" "https://api.chatwork.com/v2/rooms/${ROOM_ID}/messages"

一度定義したenvは他の場所でも参照出来ると思います。

pythonでLINE BOTを作る

作成の記事自体は色々見つかるんだけど、LINE側のWebhockの値が異なっていたりしたので自分メモ
まずはリファレンスをちゃんと読もう!(自戒)

LINE Developers

https://developers.line.biz/ja/ から

BOTユーザーは別に作るので、ここへの登録は個人のLINEアカウントで問題ない

ログイン後、新規プロバイダーを作成する

次にチャンネルを作成する
今回は普通(?)のチャットボットを作るので、Massaging APIを選択する

必要な情報を入力してチャンネルを作成する

作成後に、アプリを作る上で必要な情報は
1. Channel Secret
2. アクセストークン (ロングターム)
の2つ

Webhook送信を有効にし、Webhookを受け入れるURLを入力する(httpsのみ)

botサーバー

nginx + flaskで作りました

OSの環境変数(.bash_profileなど)にLINE_CHANNEL_SECRETとLINE_CHANNEL_ACCESS_TOKENを設定します。
書いた後は反映を忘れずに!

export LINE_CHANNEL_SECRET=xxxxxxxxxx
export LINE_CHANNEL_ACCESS_TOKEN=xxxxxxxxxxxxxxxxxx

nginxのconfigにlocationを追加する

    location ~ /何かしら {
        proxy_pass http://localhost:8000;
    }

flask アプリケーションを作る

#!/usr/bin/env python3.7
import os,json
from flask import Flask, request, make_response
import base64
import hashlib
import hmac

from linebot import (
    LineBotApi, WebhookParser
)
from linebot.exceptions import (
    InvalidSignatureError
)
from linebot.models import (
    MessageEvent, TextMessage, TextSendMessage
)

channel_secret = os.getenv('LINE_CHANNEL_SECRET', None)
channel_access_token = os.getenv('LINE_CHANNEL_ACCESS_TOKEN', None)

if channel_secret is None:
    print('Specify LINE_CHANNEL_SECRET as environment variable.')
    sys.exit(1)
if channel_access_token is None:
    print('Specify LINE_CHANNEL_ACCESS_TOKEN as environment variable.')
    sys.exit(1)

line_bot_api = LineBotApi(channel_access_token)

# Flask
app = Flask(__name__)
@app.route('/line-bot/api',methods=['post'])

def application():
    environ = request.headers
    data = json.loads(request.data)

    # シグネチャ検証(とりあえずマスク)
#    signature = environ['X_LINE_SIGNATURE']
#    hash = hmac.new(channel_secret.encode('utf-8'),
#        data.encode('utf-8'), hashlib.sha256).digest()
#    signature_from_data = base64.b64encode(hash)
#
#    print(signature)
#    print(signature_from_data)
#
    # get request body as text
    content_length = int(environ['CONTENT_LENGTH'])

    # 画像かスタンプを送られた場合
    if 'text' not in data["events"][0]["message"]:
       body = '画像、スタンプは送らないで!'

    # 教えて or おしえてと言われたら自分で探せと返す
    elif 'おしえ' in data["events"][0]["message"]["text"] or '教え' in data["events"][0]["message"]["text"]:
       body = 'オッケーグーグル!\nhttps://www.google.com/'

    # 感謝の言葉を言われたら嬉しいよね
    elif 'ありがとう' in data["events"][0]["message"]["text"]:
       body = 'お役に立てて何よりです'

    # それ以外は、とりあえずオウム返し
    else:
       body = data["events"][0]["message"]["text"]

    for i in data["events"]:
        print(i)
        line_bot_api.reply_message(
            i["replyToken"],
            TextSendMessage(text=body)
        )

    return make_response('ok'), 200

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8000)

作成後、flaskアプリケーションを起動する

スマホでLINEを起動し、LINEDeveloperのチャンネルに表示されているQRコードを読み込んで友達追加する
(確認していたら、娘からスマホを取り上げられて遊ばれた…)

dkronを触ってみた

crondで動くジョブスケジューラーがSPOFになっているサービスは多いと思う。
自社サービスも例に漏れずに、そうなっているので良さげなジョブスケジューラーが無いのか探していたところ、dkron(https://dkron.io/)というOSSがシンプルで良さげだったので、触ってみた。

まだ発展途上なのか、GUIの表示で部分部分がおかしい箇所もあるが、crondよりは十分と思える動きです。

バックグラウンドサービス

昨日作ったetcdを使います。
etcdのクラスタ設定でハマった事まとめ

インストール

cat << _EOF > /etc/yum.repos.d/dkron.repo
[dkron]
name=Dkron Pro Private Repo
baseurl=https://yum.fury.io/victorcoder/
enabled=1
gpgcheck=0
_EOF

yum -y install dkron

設定

サーバー3台でクラスタを作成する為、それぞれ設定します。

vim /etc/dkron/dkron.yml

# Dkron example configuration file
backend: etcd
advertise-addr: 192.168.33.10   # bindするアドレス
backend-machine: 127.0.0.1:2379

# 3台ともserver: trueとすると、1台だけはStatusがleavingの状態のままだった為、
# node01 , node02 はserver:true、 node03はfalseとします。
server: true
log-level: info
tags:
  role: batch
#   datacenter: east
# keyspace: dkron
# encrypt: a-valid-key-generated-with-dkron-keygen
join:
  - 192.168.33.10
  - 192.168.33.20
  - 192.168.33.30
# webhook-url: https://hooks.slack.com/services/XXXXXX/XXXXXXX/XXXXXXXXXXXXXXXXXXXX
# webhook-payload: "payload={\"text\": \"{{.Report}}\", \"channel\": \"#foo\"}"
# webhook-headers: Content-Type:application/x-www-form-urlencoded
# mail-host: email-smtp.eu-west-1.amazonaws.com
# mail-port: 25
# mail-username": mailuser
# mail-password": mailpassword
# mail-from": cron@example.com
# mail-subject-prefix: [Dkron]

設定後にdkronを起動します。

systemctl enable dkron
systemctl start dkron

VirtualBoxのポートフォワードの設定後、http://localhost:8080にアクセスすると、dkronのダッシュボードが表示されます。

ジョブは以下のようにjsonで登録します。

{
  "name": "echo-hostname",

  # timezone指定
  "timezone": "Asia/Tokyo",

  # crondと近い書き方。違うのは、[ 秒 分 時 日 月 曜日] と、秒が加わります
  "schedule": "0 * * * * *",
  "owner": "root",
  "owner_email": "", 

  # 多分、単発実行のjobの場合にtrueにするのかな
  "disabled": false, 

  # batch:xは、batchロールの中で何台のノードでジョブを実行するか
  # batch:2であれば、3台中2台のノードで実行する。
  "tags": {
    "role": "batch:2"  
  },
  "retries": 0,
  "parent_job": "",
  "processors": null,

  # 単一ノードで、ジョブの重複実行を許可するなら[allow]、許可しないなら[forbid]
  "concurrency": "forbid",
  "executor": "shell",
  "executor_config": {
    "command": "echo `date +\"%Y/%m/%d %H:%M:%S\"` `hostname`  >> /tmp/host.log",
    "shell": "true"
  },
  "status": "success"
}

確認したこと

フェイルーバーは、特に何も設定しなくても生きているノードで実行されますが、server: true としているノードが存在していないと全体が停止しますので、本番環境であればserver: trueを2台は必須と思われます。

duとdfでディスク使用が異なる場合の対応

先日、開発用サーバーがDiskFullで停止した。
原因はログの肥大化だったので、logrotateを見直して復旧させたのだけど、
duコマンドとdfコマンドのディスク使用に乖離がありすぎたので、その時に行なった対応をメモ

df -h 
Filesystem            Size  Used Avail Use% Mounted on
/dev/vda2              95G   76G   15G  85% /
tmpfs                 939M     0  939M   0% /dev/shm
du -h /
(中略)
15G /

どうやら、logrotateで圧縮したファイルを握っている残留プロセスがあり、消せないファイルとして握られているようだ。

ls -al /proc/*/fd/* | grep deleted

ls: cannot access /proc/28797/fd/255: そのようなファイルやディレクトリはありません
ls: cannot access /proc/28797/fd/3: そのようなファイルやディレクトリはありません
ls: cannot access /proc/self/fd/255: そのようなファイルやディレクトリはありません
ls: cannot access /proc/self/fd/3: そのようなファイルやディレクトリはありません
l-wx------ 1 root   root   64  3月 25 10:44 2019 /proc/12275/fd/1 -> /var/log/zabbix/zabbix_agentd.log-20181202 (deleted)
l-wx------ 1 root   root   64  3月 25 10:44 2019 /proc/12275/fd/2 -> /var/log/zabbix/zabbix_agentd.log-20181202 (deleted)
l-wx------ 1 root   root   64  3月 25 10:44 2019 /proc/12277/fd/1 -> /var/log/zabbix/zabbix_agentd.log-20181202 (deleted)
l-wx------ 1 root   root   64  3月 25 10:44 2019 /proc/12277/fd/2 -> /var/log/zabbix/zabbix_agentd.log-20181202 (deleted)
l-wx------ 1 root   root   64  3月 25 10:44 2019 /proc/12278/fd/1 -> /var/log/zabbix/zabbix_agentd.log-20181202 (deleted)
l-wx------ 1 root   root   64  3月 25 10:44 2019 /proc/12278/fd/2 -> /var/log/zabbix/zabbix_agentd.log-20181202 (deleted)
l-wx------ 1 root   root   64  3月 25 10:44 2019 /proc/12279/fd/1 -> /var/log/zabbix/zabbix_agentd.log-20181202 (deleted)
l-wx------ 1 root   root   64  3月 25 10:44 2019 /proc/12279/fd/2 -> /var/log/zabbix/zabbix_agentd.log-20181202 (deleted)
l-wx------ 1 root   root   64  3月 25 10:44 2019 /proc/12280/fd/1 -> /var/log/zabbix/zabbix_agentd.log-20181202 (deleted)
l-wx------ 1 root   root   64  3月 25 10:44 2019 /proc/12280/fd/2 -> /var/log/zabbix/zabbix_agentd.log-20181202 (deleted)
l-wx------ 1 root   root   64  3月 25 10:44 2019 /proc/12281/fd/1 -> /var/log/zabbix/zabbix_agentd.log-20181202 (deleted)
l-wx------ 1 root   root   64  3月 25 10:44 2019 /proc/12281/fd/2 -> /var/log/zabbix/zabbix_agentd.log-20181202 (deleted)
l-wx------ 1 apache apache 64  3月 25 10:44 2019 /proc/25517/fd/4 -> /var/www/hoge/log/sqllog/sql_debug.log.1 (deleted)
l-wx------ 1 apache apache 64  3月 25 10:44 2019 /proc/25517/fd/7 -> /var/www/hoge/log/sqllog/sql_debug.log.1 (deleted)
lrwx------ 1 root   root   64  3月 26 10:17 2019 /proc/26788/fd/13 -> /tmp/.ZendSem.LtZNfn (deleted)
lrwx------ 1 root   root   64  3月 26 10:17 2019 /proc/3793/fd/13 -> /tmp/.ZendSem.LtZNfn (deleted)
lrwx------ 1 root   root   64  3月 26 10:17 2019 /proc/3795/fd/13 -> /tmp/.ZendSem.LtZNfn (deleted)
lrwx------ 1 root   root   64  3月 26 10:17 2019 /proc/3943/fd/13 -> /tmp/.ZendSem.LtZNfn (deleted)
lrwx------ 1 root   root   64  3月 26 10:17 2019 /proc/4101/fd/13 -> /tmp/.ZendSem.LtZNfn (deleted)
lrwx------ 1 root   root   64  3月 26 10:17 2019 /proc/4141/fd/13 -> /tmp/.ZendSem.LtZNfn (deleted)
lrwx------ 1 root   root   64  3月 26 10:17 2019 /proc/4157/fd/13 -> /tmp/.ZendSem.LtZNfn (deleted)
lrwx------ 1 root   root   64  3月 26 10:17 2019 /proc/4158/fd/13 -> /tmp/.ZendSem.LtZNfn (deleted)
lrwx------ 1 root   root   64  3月 26 10:17 2019 /proc/4159/fd/13 -> /tmp/.ZendSem.LtZNfn (deleted)
lrwx------ 1 root   root   64  3月 26 10:17 2019 /proc/4164/fd/13 -> /tmp/.ZendSem.LtZNfn (deleted)
lrwx------ 1 root   root   64  3月 26 10:17 2019 /proc/4202/fd/13 -> /tmp/.ZendSem.LtZNfn (deleted)
lrwx------ 1 root   root   64  3月 26 10:17 2019 /proc/4203/fd/13 -> /tmp/.ZendSem.LtZNfn (deleted)
lrwx------ 1 root   root   64  3月 26 10:17 2019 /proc/4204/fd/13 -> /tmp/.ZendSem.LtZNfn (deleted)
lrwx------ 1 root   root   64  3月 26 10:17 2019 /proc/4205/fd/13 -> /tmp/.ZendSem.LtZNfn (deleted)
lrwx------ 1 root   root   64  3月 26 10:17 2019 /proc/4206/fd/13 -> /tmp/.ZendSem.LtZNfn (deleted)
lrwx------ 1 root   root   64  3月 26 10:17 2019 /proc/4207/fd/13 -> /tmp/.ZendSem.LtZNfn (deleted)
lrwx------ 1 root   root   64  3月 26 10:17 2019 /proc/4208/fd/13 -> /tmp/.ZendSem.LtZNfn (deleted)
lrwx------ 1 root   root   64  3月 26 10:17 2019 /proc/4333/fd/13 -> /tmp/.ZendSem.LtZNfn (deleted)
lrwx------ 1 root   root   64  3月 25 10:49 2019 /proc/8789/fd/13 -> /tmp/.ZendSem.LtZNfn (deleted)
lrwx------ 1 root   root   64  3月 25 10:49 2019 /proc/8791/fd/13 -> /tmp/.ZendSem.LtZNfn (deleted)
lrwx------ 1 root   root   64  3月 25 10:49 2019 /proc/8809/fd/13 -> /tmp/.ZendSem.LtZNfn (deleted)
lrwx------ 1 root   root   64  3月 25 10:49 2019 /proc/8810/fd/13 -> /tmp/.ZendSem.LtZNfn (deleted)

それが正常なプロセスであればrestartで容量がドカっと解放されるらしいが、今回は残留プロセスなので、
1. zabbix-agentの再起動
2. PID 25517 をkill
したところ、dfでみた場合の容量が正常値に戻った。

df -h
Filesystem            Size  Used Avail Use% Mounted on
/dev/vda2              95G   15G   76G  17% /
tmpfs                 939M     0  939M   0% /dev/shm
ls -al /proc/*/fd/* | grep deleted
ls: cannot access /proc/29831/fd/255: そのようなファイルやディレクトリはありません
ls: cannot access /proc/29831/fd/3: そのようなファイルやディレクトリはありません
ls: cannot access /proc/29832/fd/255: そのようなファイルやディレクトリはありません
ls: cannot access /proc/29832/fd/3: そのようなファイルやディレクトリはありません
ls: cannot access /proc/self/fd/255: そのようなファイルやディレクトリはありません
ls: cannot access /proc/self/fd/3: そのようなファイルやディレクトリはありません
lrwx------ 1 root  root  64  3月 26 10:20 2019 /proc/28922/fd/13 -> /tmp/.ZendSem.iMYqUr (deleted)
lrwx------ 1 root  root  64  3月 26 10:20 2019 /proc/28924/fd/13 -> /tmp/.ZendSem.iMYqUr (deleted)
lrwx------ 1 root  root  64  3月 26 10:20 2019 /proc/28925/fd/13 -> /tmp/.ZendSem.iMYqUr (deleted)
lrwx------ 1 root  root  64  3月 26 10:20 2019 /proc/28926/fd/13 -> /tmp/.ZendSem.iMYqUr (deleted)
lrwx------ 1 root  root  64  3月 26 10:20 2019 /proc/28927/fd/13 -> /tmp/.ZendSem.iMYqUr (deleted)
lrwx------ 1 root  root  64  3月 26 10:20 2019 /proc/28928/fd/13 -> /tmp/.ZendSem.iMYqUr (deleted)
lrwx------ 1 root  root  64  3月 26 10:20 2019 /proc/28929/fd/13 -> /tmp/.ZendSem.iMYqUr (deleted)
lrwx------ 1 root  root  64  3月 26 10:20 2019 /proc/28930/fd/13 -> /tmp/.ZendSem.iMYqUr (deleted)
lrwx------ 1 root  root  64  3月 26 10:20 2019 /proc/28931/fd/13 -> /tmp/.ZendSem.iMYqUr (deleted)
lrwx------ 1 root  root  64  3月 26 10:20 2019 /proc/28932/fd/13 -> /tmp/.ZendSem.iMYqUr (deleted)
lrwx------ 1 root  root  64  3月 26 10:20 2019 /proc/28933/fd/13 -> /tmp/.ZendSem.iMYqUr (deleted)
lrwx------ 1 root  root  64  3月 26 10:20 2019 /proc/28934/fd/13 -> /tmp/.ZendSem.iMYqUr (deleted)
lrwx------ 1 root  root  64  3月 26 10:20 2019 /proc/28935/fd/13 -> /tmp/.ZendSem.iMYqUr (deleted)
lrwx------ 1 root  root  64  3月 26 10:20 2019 /proc/28936/fd/13 -> /tmp/.ZendSem.iMYqUr (deleted)
lrwx------ 1 root  root  64  3月 26 10:20 2019 /proc/28937/fd/13 -> /tmp/.ZendSem.iMYqUr (deleted)
lrwx------ 1 root  root  64  3月 26 10:20 2019 /proc/28938/fd/13 -> /tmp/.ZendSem.iMYqUr (deleted)
lrwx------ 1 root  root  64  3月 26 10:20 2019 /proc/28939/fd/13 -> /tmp/.ZendSem.iMYqUr (deleted)
lrwx------ 1 root  root  64  3月 26 10:20 2019 /proc/28940/fd/13 -> /tmp/.ZendSem.iMYqUr (deleted)
lrwx------ 1 root  root  64  3月 26 10:20 2019 /proc/28941/fd/13 -> /tmp/.ZendSem.iMYqUr (deleted)
lrwx------ 1 root  root  64  3月 26 10:20 2019 /proc/28942/fd/13 -> /tmp/.ZendSem.iMYqUr (deleted)
lrwx------ 1 root  root  64  3月 26 10:20 2019 /proc/28943/fd/13 -> /tmp/.ZendSem.iMYqUr (deleted)

etcdのクラスタ設定でハマった事まとめ

初めてetcdを触ってみたので、個人的にハマったポイントも含めて備忘録を残します。

環境

Mac on VirtualBox
- CentOS Linux release 7.6.1810 (Core)
- kernel 3.10.0-957.10.1.el7.x86_64
- etcd Version: 3.3.11

インストール

yum でサクッと。

yum -y install etcd

config

/etc/etcd/etcd.conf

ETCD_DATA_DIR="/var/lib/etcd/default.etcd"
ETCD_LISTEN_PEER_URLS="http://192.168.33.10:2380"
ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:2379"
ETCD_NAME="node01"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://192.168.33.10:2380"
ETCD_ADVERTISE_CLIENT_URLS="http://192.168.33.10:2379"
ETCD_INITIAL_CLUSTER="node01=http://192.168.33.10:2380,node02=http://192.168.33.20:2380,node03=http://192.168.33.30:2380"
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster"
ETCD_INITIAL_CLUSTER_STATE="new"

ここまでは、
http://www.usupi.org/sysad/277.html とか
https://qiita.com/hana_shin/items/602f98bd9b153d22e50c
に記載がある通り。

この状態で、

systemctl enable etcd
systemctl start etcd

として、クラスタの状態を確認しても、なぜかpeerURLs=http://localhost:2380 とボッチ状態になる。

etcdctl member list
8e9e05c52164694d: name=node01 peerURLs=http://localhost:2380 clientURLs=http://192.168.33.20:2379 isLeader=true

解決方法

systemdの設定を見てみる

cat /usr/lib/systemd/system/etcd.service
(中略)
ExecStart=/bin/bash -c GOMAXPROCS=$(nproc) /usr/bin/etcd --name="${ETCD_NAME}" --data-dir="${ETCD_DATA_DIR}" --listen-client-urls="${ETCD_LISTEN_CLIENT_URLS}"

クラスタで起動させるには、

etcd --name node01 --listen-peer-urls http://192.168.33.10:2380 --initial-advertise-peer-urls http://192.168.33.10:2380 --initial-cluster node01=http://192.168.33.10:2380,node03=http://192.168.33.30:2380,node03=http://192.168.33.30:2380

こんな感じの設定を投げてあげる必要があるので、systemdを修正

vim /usr/lib/systemd/system/etcd.service
(中略)
# ExecStart=/bin/bash -c GOMAXPROCS=$(nproc) /usr/bin/etcd --name="${ETCD_NAME}" --data-dir="${ETCD_DATA_DIR}" --listen-client-urls="${ETCD_LISTEN_CLIENT_URLS}"

ExecStart=/bin/bash -c "GOMAXPROCS=$(nproc) /usr/bin/etcd --name ${ETCD_NAME} --listen-peer-urls ${ETCD_LISTEN_PEER_URLS} --initial-advertise-peer-urls ${ETCD_INITIAL_ADVERTISE_PEER_URLS} --initial-cluster ${ETCD_INITIAL_CLUSTER}"

・・・失敗

/var/log/messageを見てみる

Mar 25 16:39:13 node01 systemd: Starting Etcd Server...
Mar 25 16:39:13 node01 etcd: recognized and used environment variable ETCD_ADVERTISE_CLIENT_URLS=http://192.168.33.10:2379
Mar 25 16:39:13 node01 etcd: recognized and used environment variable ETCD_DATA_DIR=/var/lib/etcd/default.etcd
Mar 25 16:39:13 node01 etcd: recognized and used environment variable ETCD_INITIAL_CLUSTER_STATE=new
Mar 25 16:39:13 node01 etcd: recognized and used environment variable ETCD_INITIAL_CLUSTER_TOKEN=etcd-cluster
Mar 25 16:39:13 node01 etcd: recognized and used environment variable ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379
Mar 25 16:39:13 node01 etcd: recognized and used environment variable ETCD_STRICT_RECONFIG_CHECK=true
Mar 25 16:39:13 node01 etcd: recognized environment variable ETCD_LISTEN_PEER_URLS, but unused: shadowed by corresponding flag
Mar 25 16:39:13 node01 etcd: recognized environment variable ETCD_NAME, but unused: shadowed by corresponding flag
Mar 25 16:39:13 node01 etcd: recognized environment variable ETCD_INITIAL_ADVERTISE_PEER_URLS, but unused: shadowed by corresponding flag
Mar 25 16:39:13 node01 etcd: recognized environment variable ETCD_INITIAL_CLUSTER, but unused: shadowed by corresponding flag

EnvironmentFile=-/etc/etcd/etcd.confで読ませている設定が読まれて、、、 but unused??

ググると以下の記事があった
https://qiita.com/kobanyan/items/f8e8a3bd5406e1d290fb
変数展開出来ないのか

環境変数で読み込んでいるし、変なパラメータを外すと正常に動作した
CentOSのパッケージで読み込んだsystemdファイルがそもそも意味をなさなかったのか・・・

vim /usr/lib/systemd/system/etcd.service
#ExecStart=/usr/bin/etcd --name ${ETCD_NAME} --listen-peer-urls ${ETCD_LISTEN_PEER_URLS} --initial-advertise-peer-urls ${ETCD_INITIAL_ADVERTISE_PEER_URLS} --initial-cluster ${ETCD_INITIAL_CLUSTER}
ExecStart=/bin/bash -c "GOMAXPROCS=$(nproc) /usr/bin/etcd"

改めてクラスタメンバーの確認

etcdctl member list
254ec0e332be2d7d: name=node01 peerURLs=http://192.168.33.10:2380 clientURLs=http://192.168.33.10:2379 isLeader=true
5ddb7be38d856804: name=node02 peerURLs=http://192.168.33.20:2380 clientURLs=http://192.168.33.20:2379 isLeader=false
9cb2cdf44208202b: name=node03 peerURLs=http://192.168.33.30:2380 clientURLs=http://192.168.33.30:2379 isLeader=false

問題なさそう

子供のスマホデビューの時に行なった事

本日、小学6年生の息子にスマホを買って上げました。
子供にスマホを持たせるのは賛否両論がありますが、今は部活の連絡もLINEで連絡が回ってくる時代なので、我が家では「まだ早い!」と突っぱねるのでは無く、子供にネットリテラシーなどを覚えて貰うきっかけになればと思い、持たせることにしました。

ですが、そのままスマホを子供に渡すのは不安感が大きかったので、
・ スマホのルール
・ システム的な制限
の2つを掛け合わせて運用してみる事にしました。

スマホのルール

色々なサイトを参考にしましたが、基本的にはスマホ18の約束を真似て作っています。
作成したスマホルール

我が家のルールは、緩い方だと思います。
そこは、子供への信頼や、後述するシステム的な制限を入れているというのもあります。

何故こんなルールを作る必要があるのか?
子供を制したいのでは無く、自由にさせてあげたい。
だからこそ、守って欲しい最低限のルールしか書いていません。

また、我が子は、「例えば・・・」の話をした方が理解しやすいみたいで、沢山例を上げました。

そこをちゃんと理解して貰わないと、個々の内容に行く前に、
「俺の事を信用していないの!?」となります。

システム的な制限

と言っても大げさな物ではありません。

我が子に買って上げたスマホは、Androidのミドルクラス(3万〜4万)のスマホです。
(iphoneが欲しがっていましたが、スマホデビューで7万円以上もする機種代を払うのは勿体無さすぎ)

Androidなので、今年から日本で使えるようになった「google ファミリーリンク」を使いました。

ファミリーリンクで、とりあえずは以下の制限を行なっています。
⑴ 平日は22:00 – 6:00 はスマホの強制ロック
⑵ 金曜日、休日は23:00 – 6:00 はスマホの強制ロック
⑶ プリインンストールだろうがシステムアプリだろうが、使わなくていいアプリの無効化
⑷ Playストアの購入リクエスト
⑸ 可能な限り、子供向けコンテンツのみ表示

(1)(2)は、1週間のスケジュール設定が行えますが、祝日や冬休みの対応は、変更が必要です。
変更は、親のスマホにインストールしたファミリーリンクアプリから出来ます。

(3)は、文字通り強制的に画面をロックし、緊急電話しか行えなくなります。
子供がパスワードや指紋認証でロックを解除した場合、以下のように表示されるので子供にも分かりやすいですね

(4)は、子供がPlayストアでアプリをインストールしたい場合、保護者に承認を送る事が出来ます。
何が良いかというと、

「パパ、◯◯ってゲームインストールしておいて!」
「疲れてるから週末にやって上げるよ〜」
「それじゃ遅いよ!!」

という嫌われポイントが少なくなります。
子供からリクエストが来たら、電車の中だろうと確認して「承認」をすれば良いだけですからね。
有料のコンテンツは、奥さんに確認してから承認、却下をするだけですし。

⑸ 可能な限り、子供向けコンテンツのみ表示
恐らく、chromeとかgoogleのアプリ限定で機能するのではないでしょうか?
実際、LINEのトーク内のURLをクリックしてWebブラウザを起動した場合、制限を行なっていてもアダルトサイトなど見えてしまう状態です。

ネットワーク周りのフィルタリングを、もう少し強化する為に、WifiのアクセスポイントをIO-DATAのWN-AX2033GR2に変更しました。
このモデルは、L7での簡易フィルタリング機能が備わっているらしく、

従来モデルでご好評の悪質サイトやフィッシングサイトをフィルタリングする「ネットフィルタリング」機能(5年無料)や、お子様の利用時間を制限する「ペアレンタルコントロール」機能はもちろん、パソコンやスマートフォン、ゲーム機など、Wi-Fi接続端末を管理できる「Wi-Fiマモル」機能を搭載。よりセキュリティ効果を高めることができます。

との事です。

こちらはネットワークでの制限になるので端末のMACアドレスが必要になります。
設定してみると不適切なコンテンツを閲覧しようとした場合に、Wifiのエラー画面に遷移します。

ec2のsquidでグローバルIPを分散するProxyを作る

これはAdventure Advent Calendar 2018の13日目の記事です。(後追い)

プロキシサーバーと言えばsquidと思い浮かべるように、どメジャーなミドルウェアですが、今回はsquidを使って「複数のグローバルIPでインターネットにアクセスするプロキシサーバー」を作ってみました。

複数人でプロキシサーバーを使ったアクセスを行うと、すぐに接続元IPアドレスによるアクセス拒否が行われてしまうサイトにアクセスする際にどうぞ。

ec2を立てる

インスタンスのサイズによって、インターフェイスあたりの IPv4 アドレスの最大数が異なりますので以下を参考にして下さい。

https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/using-eni.html

今回はt2.largeにし、
eth0の18080ポートで、外部からの通信を受け入れ、
eth1に9個のセカンダリプライベートIPをアサインして、それぞれにElasticIPを付け、計10個のIPアドレスからインターネットに出るようにしたいと思います。

インスタンスを作る上で注意する点は、最初からeth1を作成する事ぐらいです。
(後からでも追加出来ますが、インスタンスを停止してeth1をアタッチし、また起動するという手間があります)

なお、今回はAmazon Linux 2 AMI (HVM), SSD Volume Typeで作成しています。

インスタンスが起動したら、メニューからIPアドレスの管理を選択します。

eth1側に必要な数分、プライベートIPを割り当てます。
ここでは、DHCPでアドレスが割り当たりませんので、手動で入力していく必要があります。
(当たり前ですが、eth1が所属するサブネットの範囲で割り当てて下さい)

なお、eth1のeni-で始まる管理番号は、次で利用しますので控えておいて下さい。

忘れないように、セキュリティグループの穴あけもやっておきましょう。

ElasticIPを発行する

ElasticIPを発行します。
ここで注意する点は、
eth1のセカンダリIPアドレスに割り当てるElasticIPは、ネットワークインターフェイスでのみ検索、表示出来る。
という点です。
ラジオボタンがインスタンスのままだと表示されません。

複数のグローバルIPアドレス分、この割り当て作業を繰り返します。

squidを立てる

最後にsquidの設定です

sudo yum -y install squid

以下は/etc/squid/squid.confの抜粋です

 # proxyを使う接続元IP
acl mynetwork src xxx.xxx.xxx.xxx/32

 # 今回10個のIPをランダムで使う
acl balance random 1/10
balance_on_multiple_ip on

# LISTENポート
http_port 18080

# 帯域を潰されたくないので、特定の動画サイトのドメインは見せないようにする
acl blacklist dstdomain "/etc/squid/blacklist"
# 非読化
visible_hostname unkown
forwarded_for off
request_header_access X-FORWARDED-FOR deny all
request_header_access Via deny all
request_header_access Cache-Control deny all

# ファイルディスクリプタの上限変更
max_filedesc 65535

# ElasticIPに紐付けたセカンダリIP
tcp_outgoing_address 172.30.2.211 balance 
tcp_outgoing_address 172.30.2.212 balance 
tcp_outgoing_address 172.30.2.213 balance 
tcp_outgoing_address 172.30.2.214 balance 
tcp_outgoing_address 172.30.2.215 balance 
tcp_outgoing_address 172.30.2.216 balance 
tcp_outgoing_address 172.30.2.217 balance 
tcp_outgoing_address 172.30.2.218 balance 
tcp_outgoing_address 172.30.2.219 balance 
tcp_outgoing_address 172.30.2.220 balance 

/etc/squid/blacklistには主要な動画サイトのドメインを書いておきます

www.youtube.com
www.nicovideo.jp
gyao.yahoo.co.jp
www.happyon.jp
www.netflix.com

squidの自動起動と開始

sudo systemctl enable squid
sudo systemctl start squid

エラーが無く起動が出来れば、eth0に割り当てたElasticIPの18080をブラウザのプロキシに設定し、IP確認くんなどで通信確認とeth1のセカンダリIPアドレスに割り当てたElasticIPで通信が行えているか確認をしてみて下さい。