2022年4月28日木曜日

docker で起動した Gitlab の production_json.log を fluentd で elasticsearch に飛ばす方法

docker で起動した Gitlab の production_json.log を fluentd で elasticsearch に飛ばす方法

概要

Gitlab のコンテナの標準出力のログは JSON 形式ではないので docker の fluentd ドライバが使えません
なのでコンテナ内に出力されるアプリケーションのログファイルを見る必要があります

ポイントはコンテナ内のログファイルをホストにマウントする点です

環境

  • Gitlab-ee 14.7.7
  • fluentd 1.3.2
  • fluent-plugin-elasticsearch 4.3.3
  • elasticsearch 7.17.1
  • elasticsearch 7.17.3
  • kibana 7.17.3

ElasticSearch 起動

  • docker run -d -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:7.17.3

Kibana 起動

  • docker run -d -e ELASTICSEARCH_HOSTS=http://192.168.100.10:9200 -p 5601:5601 docker.elastic.co/kibana/kibana:7.17.3

fluentd ビルド&起動

  • vim Dockerfile
FROM fluent/fluentd

RUN apk add --update --virtual .build-deps \
        sudo build-base ruby-dev \
 && sudo gem install \
        elasticsearch -v 7.17.1 \
 && sudo gem install \
        fluent-plugin-elasticsearch -v 4.3.3 \
 && sudo gem sources --clear-all \
 && apk del .build-deps \
 && rm -rf /var/cache/apk/* \
           /home/fluent/.gem/ruby/2.5.0/cache/*.gem
  • vim fluent.conf
<source>
  @type tail
  format json
  path /fluentd/gitlab_log/production_json.log
  pos_file /fluentd/gitlab_log/production_json.log.pos
  tag gitlab.production
  keep_time_key true
</source>

<match gitlab.production>
  @type copy
  <store>
    @type stdout
  </store>
  <store>
    @type elasticsearch
    host 192.168.100.10
    port 9200
    index_name gitlab.production
    type_name fluentd
	    logstash_format true
    time_key time
  </store>
</match>
  • docker build -t my_fluentd .
  • docker run -d -v $(pwd):/fluentd/etc -v /path/to/log/gitlab-rails:/fluentd/gitlab_log -e FLUENTD_CONF=fluent.conf -e FLUENT_UID=998 my_fluentd

ポイント

  • Gitlab のログをホスト側でマウントしてそれを tail で飛ばす
  • 上記の場合は fluentd コンテナの /fluentd/gitlab_log にログをマウントしている
  • マウント先は /fluentd 配下でないと権限がないと言われて怒られる
  • また fluent ユーザの UID は 998 にしている、998は Gitlab 上で動作している git ユーザの UID でログファイルの権限が git ユーザの権限になっている、fluent ユーザのデフォルトの UID は 1000 になっており 1000 のまま fluentd コンテンを起動するとログの権限が 1000 になり Gitlab からログが書き込めずエラーになるのでそれの対応になる
  • production_json.log にはタイムスタンプ用に time フィールドがあるが keep_time_key: true を設定しないと消えるので注意
  • Elasticsearch のバージョンに合わせて elasticsearch-ruby のバージョンも合わせる必要がある
  • ログファイルの権限と fluentd 側の権限 (uid, gid) は合わせる必要がありそう

最後に

他のログも同じように転送することができます

2022年4月27日水曜日

Gitlab で LDAP サーバと連携して LDAP にいるユーザでログインする方法

Gitlab で LDAP サーバと連携して LDAP にいるユーザでログインする方法

概要

Gitlab + LDAP で LDAP に存在するユーザでログインする方法を試してみました

環境

  • Ubuntu 18.04
  • docker 20.10.7
  • osixia/openldap 1.5.0
  • Gitlab-ee 14.7.7

LDAP サーバの構築

今回は docker で構築しました
過去に紹介しているのでそちらを参考に構築してください

gitlab.rb の編集

LDAP 連携の部分だけ記載しています

gitlab_rails['ldap_servers'] = {
  'main' => {
    'label' => 'LDAP',
    'host' =>  '192.168.100.10',
    'port' => 389,
    'uid' => 'uid',
    'encryption' => 'plain',
    'verify_certificates' => false,
    'bind_dn' => 'cn=admin,dc=my-company,dc=com',
    'password' => 'xxx',
    'tls_options' => {
      'ca_file' => '',
      'ssl_version' => '',
      'ciphers' => '',
      'cert' => '',
      'key' => ''
    },
    'timeout' => 10,
    'active_directory' => false,
    'allow_username_or_email_login' => false,
    'block_auto_created_users' => false,
    'base' => 'dc=my-company,dc=com',
    'user_filter' => '',
    'attributes' => {
      'username' => ['uid', 'userid', 'sAMAccountName'],
      'email' => ['mail', 'email', 'userPrincipalName'],
      'name' => 'uid',
    },
    'lowercase_usernames' => false,
  }
}

変更できたら gitlab-ctl reconfigure などで設定を反映しましょう

  • docker-compose up -d

各項目の説明

  • label・・・Gitlabのログイン画面で表示するラベルを設定します
  • host・・・LDAPサーバのIPまたはホスト名を記載します
  • port・・・LDAPサーバの接続ポートを記載します
  • uid・・・sAMAccountName or uid or userPrincipalName のどれかを記載します、LDAP側に存在する属性を記載します
  • encryption・・・start_tls or simple_tls or plain のどれかを記載します、今回は tls は使わないので plain を設定します
  • verify_certificates・・・今回は tls は使わないので false を設定します
  • bind_dn・・・探索する dn を記載します、ldapsearch で使用する -D オプションの値を記載します
  • password・・・admin ユーザのパスワードを記載します
  • tls_options・・・tlsを使う場合に使用します、今回は使用しないのですべて空にしています
  • timeout・・・LDAPにクエリを投げた際のタイムアウトを秒単位で指定します
  • active_directory・・・AD経由のLDAPの場合は true を設定します、今回は OpenLDAP なので false を設定します
  • allow_username_or_email_login・・・ユーザ名またはメールアドレスでのログインを許可します、true にした場合はメールアドレスのアットマーク以降は自動的に省略されます
  • block_auto_created_users・・・ユーザ作成時にブロックユーザとして自動で作成します、面倒なので基本は false にします
  • base・・・探索する base を記載します、ldapsearch で使用する -b オプションの値を記載します
  • user_filter・・・検索する際のフィルタを指定できます、'(employeeType=developer)' こんな感じの指定ができます
  • attributes・・・ログインに成功した際に Gitlab 側に登録するユーザ属性とLDAPのユーザ属性のマッピングを指定します
  • lowercase_usernames・・・自動的にユーザ名を小文字に変換します

その他の項目については参考サイトにある公式のドキュメントを確認してください

動作確認

Gitlab にアクセスして LDAP の項目が増えていることを確認します
あとは LDAP に存在するユーザでログインできるか確認しましょう

注意点

LADP 経由でログインはしていますが一度 Gitlab 側にユーザが作成されると LDAP 側のユーザを削除しても Gitlab 側には残り続けてしまいます (ログインはできなくなります)

もし LDAP とユーザ情報を同期したい場合はこちらを参考に設定してください

billy ユーザについて

パスワードは不明なので自分で ldif ファイルを変更してパスワードを設定してください

参考サイト

2022年4月26日火曜日

docker で phpAdminLDAP を構築する

docker で phpAdminLDAP を構築する

概要

前回 docker で OpenLDAP サーバを構築しました
今回は管理UIである phpAdminLDAP も docker で構築してみたいと思います

環境

  • Ubuntu 18.04
  • docker 20.10.7
  • osixia/openldap 1.5.0
  • osixia/phpldapadmin 0.9.0

起動

以下のコマンドで起動します
192.168.100.10 は OpenLDAP サーバの IPアドレスを記載してください

docker run -p 6443:443 \
  -e PHPLDAPADMIN_LDAP_HOSTS=192.168.100.10 \
  --name ldap-admin \
  -d osixia/phpldapadmin:0.9.0

アクセス

https://192.168.100.10:6443 でアクセスできます
https プロトコルなので注意しましょう

アクセスするとログインしていない状態になるので左メニューからログインを選択します

dn と admin ユーザのパスワードを入力することでログインできます

ログインすると指定した dn 内のユーザが確認できます

最後に

LDAP のインタフェースはコマンドが基本ですがややこしいので GUI のインタフェースがあると便利かなと思います

参考サイト

2022年4月25日月曜日

docker で OpenLDAP サーバ構築

docker で OpenLDAP サーバ構築

概要

docker で OpenLDAP サーバを構築してみました
過去に CentOS 上に構築しましたが今回は docker を使います

環境

  • Ubuntu 18.04
  • docker 20.10.7
  • osixia/openldap 1.5.0

ldap サーバ起動

LDAP 関連の設定は環境変数で設定できます
外部からアクセスする想定の場合は 389 と 636 ポートをバインドします

docker run \
  -e LDAP_ORGANISATION="My Company" \
  -e LDAP_DOMAIN="my-company.com" \
  -e LDAP_ADMIN_PASSWORD="xxx" \
  -p 389:389 -p 636:636 \
  --name ldap-server \
  -d osixia/openldap:1.5.0

作成した dc があるか検索

LDAP_DOMAIN で指定した dc があるか検索してみましょう
検索するときは -b オプションを使います

docker exec ldap-server \
  ldapsearch -x -H ldap://localhost -b dc=my-company,dc=com -D "cn=admin,dc=my-company,dc=com" -w xxx

ユーザを新規で登録する

ldif ファイルを作成して新規でユーザ登録してみます
dn の部分は LDAP_DOMAIN に合わせてください

  • vim new-user.ldif
dn: uid=billy,dc=my-company,dc=com
uid: billy
cn: billy
sn: 3
objectClass: top
objectClass: posixAccount
objectClass: inetOrgPerson
loginShell: /bin/bash
homeDirectory: /home/billy
uidNumber: 14583102
gidNumber: 14564100
userPassword: {SSHA}j3lBh1Seqe4rqF1+NuWmjhvtAni1JC5A
mail: billy@my-company.com
gecos: Billy User

一旦コンテナにコピーします

  • docker cp new-user.ldif ldap-server:/container/service/slapd/assets/test/new-user.ldif

あとは ldapadd で登録すれば OK です
登録する際は -b は不要です

docker exec ldap-server \
   ldapadd -x -H ldap://localhost -D "cn=admin,dc=my-company,dc=com" -w xxx -f /container/service/slapd/assets/test/new-user.ldif

先程の検索コマンドで検索すると指定の dn 配下にユーザが登録されていることが確認できると思います

ユーザを削除する

最後にユーザを削除しましょう
これも ldif ファイルを使います
削除する場合の ldif ファイルは形式が少し違うので注意しましょう
削除対象の dn のエントリを1行記載すれば OK です

  • vim delete-user.ldif
uid=billy,dc=my-company,dc=com

これもコンテナにコピーします

  • docker cp delete-user.ldif ldap-server:/container/service/slapd/assets/test/delete-user.ldif

あとは ldapdelete コマンドでユーザ削除します

docker exec ldap-server \
   ldapdelete -x -H ldap://localhost -D "cn=admin,dc=my-company,dc=com" -w xxx -f /container/service/slapd/assets/test/delete-user.ldif

最後に

データを永続化したりクラスタ化したり TLS 対応もできます
詳細は参考サイトにある公式のページを御覧ください

あと外部からアクセスできるか ldap-utils などを使って確認すると良いと思います

参考サイト

2022年4月22日金曜日

Ruby3 以降では open-uri は URI.open を使用する必要がある

Ruby3 以降では open-uri は URI.open を使用する必要がある

概要

タイトルの通りです
open は Kernel.open でローカルファイルを開くのに使います
外部の URL を開く場合は URI.open を使います

サンプルコード

require 'open-uri'

open('https://hawksnowlog.blogspot.com/')

Ruby3 だと以下のエラーになるので

app.rb:3:in `initialize': No such file or directory @ rb_sysopen - https://hawksnowlog.blogspot.com/ (Errno::ENOENT)

以下のように書き換える

require 'open-uri'

URI.open('https://hawksnowlog.blogspot.com/')

ちなみに Ruby 2.6.5 だと当初の書き方でも動作する

参考サイト

2022年4月21日木曜日

Ruby Resque 超入門 (Rails なし)

Ruby Resque 超入門 (Rails なし)

概要

Resque は redis を使ったジョブキューツールで非同期処理を実現することができます
同じようなツールに Sidekiq があります

今回は Resque を単体で使いどんな感じで使うのかを試してみました
よく Resque を Rails と一緒に紹介している記事はあるのですが本記事では Rails は登場しません

環境

  • macOS 11.6.5
  • Ruby 3.1.1p18
    • resque 2.2.1

準備

  • vim Gemfile
gem "resque"
gem "rake"
  • bundle install

redis 起動

  • brew services start redis

Rakefile

  • vim rake
require 'resque/tasks'
require './job.rb'

サンプルコード: ワーカー側

気をつけることはただ一つで self.perform を実装します

  • vim job.rb
require 'json'

class MessageJob
  @queue = :default

  def self.perform(msg)
    sleep 10
    data = {
      msg: msg,
      date: Time.now
    }
    puts data.to_json
  end
end
  • QUEUE=default bundle exec rake resque:work

サンプルコード: 呼び出す側

  • vim app.rb
require 'resque'
require './job.rb'

Resque.enqueue(MessageJob, "Hello resque!")
  • bundle exec ruby app.rb

これでワーカー側にログが流れるのが確認できると思います

最後に

結構簡単に使えました

なぜか Rails と一緒に紹介する記事ばかりだったので Resque 単体で動作させる方法を紹介しました

Sidekiq も同じような感じで使えたので実装に関してはあまり変わらないのかもしれません
あと違いがあるとすれば管理用のクラスや UI があるのかや優先度キュー、パイプライン機能、サポートしているバックグランドの種類あたりかなと思います

Tips: Pipelining commands on a Redis instance is deprecated and will be removed in Redis 5.0.0.

実行時に上記の警告が表示されます
Resque が依存している redis-namespace という gem がまだ対応していないのが原因です

参考 - https://github.com/resque/redis-namespace/issues/193

いずれ対応されると思いますが自分で対応したい場合は Redis.silence_deprecations = true を設定すればとりあえず抑制できます

  • vim job.rb
require 'json'
require 'redis'

Redis.silence_deprecations = true

class MessageJob
  @queue = :default

  def self.perform(msg)
    sleep 3
    data = {
      msg: msg,
      date: Time.now
    }
    puts data.to_json
  end
end

エンキューする app.rb 側も同様です

参考サイト

2022年4月20日水曜日

rake 超入門

rake 超入門

概要

rake は Ruby 製のタスク管理ツールです
make の Ruby 版で rake という名前になっています
今回は導入としてサンプルを元に基本的な使い方を紹介します

環境

  • macOS 11.6.5
  • ruby 3.1.1p18

ruby スクリプトをコールするタスク

まずは基本の形です
task メソッドにリテラルでタスク名を渡します
そしてブロックでタスクの内容を記述します

rake にはいくつかのリソースが定義されており ruby リソースを使うと指定したパスにある ruby ファイルを実行することができます

desc は rake -T でタスクの一覧を表示する対象となるために必要です
公開するタスクには基本的に desc を使って説明文を挿入しましょう

desc "Execute ruby script."
task :run_script do
  ruby "app.rb"
end

実行する場合は以下で実行できます

  • rake run_script

タスクの依存関係を定義する

例えばあるタスクの前に別のタスクを実行したいことがあると思います
その場合はタスクの定義をハッシュで渡します

desc "Execute ruby script."
task :run_script do
  ruby "app.rb"
end

desc "Show hello message."
task echo: [:run_script] do |t|
  sh "echo Hello #{t.name} task."
end

echo: [:run_script] の部分がハッシュになっているのがわかります
以下の書き方でも動作します

echo_task = {
  echo: [:run_script]
}
desc "Show hello message."
task echo_task  do |t|
  sh "echo Hello #{t.name} task."
end

ちなみに sh リソースは指定されたコマンドを実行することができるリソースです

  • rake echo

タスクに引数を渡す

タスクに引数を渡すこともできます
タスク名の次の引数で配列で引数名を渡します
ブロック内で参照する場合は

args.name
のように参照できます

with_defaults を使うとデフォルト値を設定できます

desc "Show args."
task :puts, [:name, :age]  do |t, args|
  args.with_defaults(:name => "hawk", :age => 10)
  puts "name => #{args.name}, age => #{args.age}"
end

シングルクオートでタスク名を囲っているのは zsh: no matches found 対策です

ファイルタスク

いままでは task を使ってきましたが file というタスクも定義できます
ファイルやディレクトリの操作に特化したタスクで依存しているファイルタスクで指定したファイル名がすでに存在する場合は依存タスクを実行しないという特性があります

file "date.txt" do
  touch "date.txt"
end

desc "Print current time to file if does not exist."
file "print_now" => ["date.txt"] do |t|
  sh "date > date.txt"
end

これで

  • rake print_now

をすると一回目は touch が実行されますが二回目は touch が実行されないのが確認できると思います

namespace でタスクをまとめる

タスク同士をまとめて管理したい場合は namespace を使うと管理しやすくなります

namespace :test do
  desc "Print hello."
  task :echo do
    puts "hello."
  end
end

タスクをクラスとして定義する

Rake::Task を継承してやることもできそうですがいろいろ面倒なのでタスクの内容を単純にクラスにしてあげることでも実現できます

class User
  def initialize(name, age)
    @name = name
    @age = age
  end

  def show
    puts "name => #{@name}, age => #{@age}"
  end
end

desc "Show user profile."
task :show_profile do
  user = User.new("hawk", 20)
  user.show
end

この User クラスの部分だけ他のファイルに分割して管理してあげるとタスクの内容がクラスに分割できるので良いかなと思います

最後に

単純に Ruby を書くことができるので例えばクラスを定義して何かしたり net/http などを使って外部にアクセスするようなタスクを定義することもできます

rake 自体にいろいろなデフォルトメソッドが用意されているのでこれでも十分かなと思います

rake のデフォルトメソッド一覧 -> https://ruby.github.io/rake/table_of_contents.html#methods

あとはクラスとして定義したいというケースもあるかなと思います
書けなくはないっぽいですがかなり面倒な感じなのでクラスとしてタスクを書きたい場合はタスクの内容をクラスとして定義するか rake にしないで単純に ruby ファイルとしてタスクの内容を定義しましょう

rake タスクとしてわざわざタスク化するメリットとしては以下があるかなと思います

  • rake -T でタスク化したものをまとめて確認することができる
  • rake のデフォルトメソッドが使える
  • make は書けないが ruby は書ける人がタスク定義したい場合

参考サイト

2022年4月19日火曜日

cron + mysqldump で定期バックアップする方法

cron + mysqldump で定期バックアップする方法

概要

mysqldump を cron に仕込む際のテクニックを紹介します

環境

  • Ubuntu 18.04

cron

30 5 * * * cd /home/user; /usr/bin/mysqldump -h 192.168.100.1 -u db_user --all-databases --skip-add-drop-table > /home/user/dump/`date +"\%Y\%m\%d"`_dump.sql

.my.cnf

  • vim /home/user/.my.cnf
[mysqldump]
user=db_user
password=xxxxxx

解説

ダンプ時のオプションは --all-databases--skip-add-drop-table にしています
前者は全データベースのダンプで後者は発行されるダンプファイルに drop table 文を含めないようにするオプションです

ダンプファイルは毎日取得することを想定しているので日時情報をファイル名に含めています
その際に date コマンドを使用していますが date コマンドのフォーマット記法「% (パーセント)」は cron ないではエスケープしなければならないので注意しましょう

mysqldump を取得する際には必ずと言っていいほどパスワードが必要になります
コマンドに直接埋め込んでもいいのですが警告が出るのでファイルに記載します
その際に mysqldump コマンドを実行しているパスに「.my.cnf」という隠しファイルを置いておくと自動でそれを読み込んでくれます
ここに認証情報が記載できるので mysql のユーザ名とパスワードを記載しましょう

2022年4月18日月曜日

Gitlab + git push option で CI にパラメータを渡す方法

Gitlab + git push option で CI にパラメータを渡す方法

概要

Gitlab CI で「この push は CI をスキップさせたい」みたいなことがある思います
その場合には git push option を使います

環境

  • Gitlab EE 14.9.3
  • git 2.17.1

CI をスキップする

  • git push -u origin -o ci.skip master

ci.skip を指定します

特定の変数を設定する

  • git push -u origin -o ci.variable="MSG=Hello git push option" master
image: python:3.10.2-buster

stages:
  - test

test:
  stage: test
  variables:
    TZ: Asia/Tokyo
  script:
    - echo $MSG

他には

マージリクエスト関連のオプションがあります
例えば merge_request.create は自動的にマージリクエストを作成してくれます

参考サイト

2022年4月16日土曜日

S3 の Web ベースクライアントは MinIO の gateway s3 を使うのがいいかも

S3 の Web ベースクライアントは MinIO の gateway s3 を使うのがいいかも

概要

Amazon の S3 のクライアントツールには Blackberry や Cyberduck などマシンにインストールして使うツールがいくつかあります

ただわざわざインストールして使うのも面倒なので Web ベースのクライアントツールを探していました

MinIO の Web コンソールが s3 や azure に対応しているようなので使ってみました

環境

  • Ubuntu 18.04
  • docker 20.10.7
  • minio RELEASE.2022-04-12T06-55-35Z

準備

事前に S3 のアクセスキーとシークレットキーを取得しておいてください

S3 用の Web クライアントとして起動

docker が使えるので docker を使います

docker run --rm -p 9100:9100 -p 9000:9000 --name minio-s3 -e "MINIO_ROOT_USER=AKIxxx" -e "MINIO_ROOT_PASSWORD=xxx" quay.io/minio/minio gateway s3 --console-address ":9100"

MINIO_ROOT_USER と MINIO_ROOT_PASSWORD にアクセスキーとシークレットキーを設定して起動します

これで localhost:9100 にアクセスするとログイン画面が表示されます
ログインユーザとパスワードは s3 のアクセスキーとシークレットキーでログインできます

S3 互換のクラウドストレージサービスのクライアントとして起動

もし S3 互換のクラウドストレージサービスであればエンドポイントを変更することで Web クライアントとして使うことができます

docker run --rm -p 9100:9100 -p 9000:9000 --name minio-s3 -e "MINIO_ROOT_USER=AKIxxx" -e "MINIO_ROOT_PASSWORD=xxx" quay.io/minio/minio gateway s3 https://jp-east-1.storage.api.nifcloud.com:443 --console-address ":9100"

例えばニフクラのオブジェクトストレージサービスであれば上記のようにエンドポイントを指定することでアクセスできます

アクセスキーとシークレットキーは各クラウドストレージサービスで発行されたものを使ってください

最後に

S3 の Web ベースクライアントがなかったので調査してみました
起動ログを見ると gateway のコードはなくなります的なことが書いてあるのでもしかすると使えなくなるのかもしれないですが、、

参考サイト

2022年4月15日金曜日

docker-compose で node_exporter を起動する方法

docker-compose で node_exporter を起動する方法

環境

  • Ubuntu 18.04
  • docker 20.10.7
  • node-exporter 1.3.1

docker-compose

version: '3.8'

services:
  node_exporter:
    image: quay.io/prometheus/node-exporter:latest
    container_name: node_exporter
    command:
      - '--path.rootfs=/host'
    network_mode: host
    pid: host
    restart: unless-stopped
    volumes:
      - '/:/host:ro,rslave'

上記は host ネットワークで起動します
ports を使う場合は以下の通り

version: '3.8'

services:
  node_exporter:
    image: quay.io/prometheus/node-exporter:latest
    container_name: node_exporter
    command:
      - '--path.rootfs=/host'
    ports:
      - '9100:9100'
    restart: unless-stopped
    volumes:
      - '/:/host:ro,rslave'

ただ ports を使う場合はネットワークのメトリックが node_exporter のコンテナのメトリックになるので注意してください

ちゃんとホストのネットワークインタフェースを監視したい場合は network_mode: host を使用します

参考サイト

2022年4月14日木曜日

jq で配列の中の辞書内にある特定のカラムのみを CSV にする方法

jq で配列の中の辞書内にある特定のカラムのみを CSV にする方法

概要

CSV にするテクニックを紹介します

環境

  • macOS 11.6.5
  • jq 1.6

データ

  • vim test.json
{
  "user": {
    "codes": [
      {
        "name": "Ruby",
        "framework": "Rails"
      },
      {
        "name": "Swift",
        "framework": "SpriteKit"
      },
      {
        "name": "Python",
        "framework": "flask"
      }
    ]
  }
}

nameカラムを CSV にするコマンド

cat test.json | jq -r '[.user.codes[].name] | @csv'

ポイントは

  • 結果を [] で配列にする
  • @csv を使って配列を CSV として出力する

ちなみに更にここからダブルクオーテーションを削除したい場合はパイプで | sed 's/"//g' を追加します

2022年4月13日水曜日

Sinatra で JavaScript を文字列として配信する方法

Sinatra で JavaScript を文字列として配信する方法

概要

わざわざファイルにして public 配下に置いて配信しないでも Sinatra で直接記載することもできます

環境

  • macOS 11.6.5
  • Ruby 3.1.1p18
  • Sinatra 2.2.0

サンプルコード

# coding: utf-8
require 'sinatra'

class WebApp < Sinatra::Base

  # HTMLのテンプレートを管理
  template :index do
    <<~EOF
      <html>
        <body>
          <div id="msg"></div>
          <script src="/js/sample.js"></script>
        </body>
      </html>
    EOF
  end

  # / に来たら :index テンプレートを返却してHTMLを表示する
  get '/' do
    erb :index
  end

  # :index テンプレート内で参照している /js/sample.js をここで配信する
  get '/js/sample.js' do
    content_type 'text/javascript'
    <<~EOF
      var msg = document.getElementById("msg");
      console.log(msg);
      document.getElementById("msg").innerHTML = "hoge";'
    EOF
  end
end

ヒアドキュメントで記載すれば複数行でも書けます
簡単な JavaScript であればこれでもいいかも

2022年4月12日火曜日

Python pickle でオブジェクトをバイナリファイルに保存してオブジェクトの状態を保存する

Python pickle でオブジェクトをバイナリファイルに保存してオブジェクトの状態を保存する

概要

Python の pickle はオブジェクトをそのままバイナリファイルに保存することができます
またバイナリファイルからオブジェクトを生成することもできます

オブジェクトのデータを永続化するのにデータベースやキャッシュを用意するのが普通ですがそこまでやる必要がない場合には便利です

環境

  • macOS 11.6.5
  • Python 3.10.2

サンプルコード

  • vim app.py
import pickle
import sys

class User():
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def show(self):
        print(self.name)
        print(self.age)


if __name__ == '__main__':
    name = "hawksnowlog"
    if sys.argv[1] == 'save':
        # 保存
        with open('./{}.pickle.bin'.format(name), 'wb') as f:
            user = User(name, 10)
            pickle.dump(user, f)
    elif sys.argv[1] == 'load':
        # 取得
        with open('./{}.pickle.bin'.format(name), 'rb') as f:
            user = pickle.load(f)
            user.show()

dump で保存し load で読み込みます

動作確認

  • python app.py save
  • python app.py load

保存したオブジェクトか再現されることが確認できると思います

ただ状態を保存しているだけでオブジェクトIDは変わるので注意してください

参考サイト

2022年4月11日月曜日

rewrite_tag_filter を使って特定のログを Slack に通知する方法

rewrite_tag_filter を使って特定のログを Slack に通知する方法

概要

前回 fluentd-plugin-slack を使って fleuntd に来たログを slack に通知する方法を紹介しました
今回は特定の文字列を含むログだけを slack に通知する方法を紹介します

grep ではなく rewrite_tag_filter プラグインを使います

環境

  • Ubuntu 18.04
  • docker 20.10.7
  • fluentd 1.3.2

Dockerfile

  • vim Dockerfile
FROM fluent/fluentd

RUN apk add --update --virtual .build-deps sudo build-base ruby-dev \
 && sudo gem install fluent-plugin-slack fluent-plugin-rewrite-tag-filter \
 && sudo gem sources --clear-all \
 && apk del .build-deps \
 && rm -rf /var/cache/apk/* /home/fluent/.gem/ruby/2.5.0/cache/*.gem

イメージ作成

  • docker build -t my_fluentd .

fleunt.conf 作成

  • vim fluent.conf
<source>
  @type forward
  port 24224
  bind 0.0.0.0
</source>

<match docker.**>
  @type copy
  <store>
    @type stdout
  </store>
  <store>
    @type rewrite_tag_filter
    <rule>
      key log
      pattern /notify slack/
      tag slack.${tag}
    </rule>
  </store>
</match>

<match slack.**>
  @type slack
  webhook_url https://hooks.slack.com/services/xxx/xxx/xxxxxx
  channel private
  username test
  message_keys log
  icon_emoji :ghost:
  flush_interval 60s
</match>

今回は log キー内に「notify slack」という文字列を含むログだけを slack に通知します

fluentd コンテナ起動

  • docker run -d -p 24224:24224 -p 24224:24224/udp -v $(pwd):/fluentd/etc -e FLUENTD_CONF=fluent.conf --name my_fluentd my_fluentd

動作確認

  • docker run --rm --log-driver=fluentd --log-opt fluentd-address=192.168.100.1:24224 --log-opt tag="docker.{{.Name}}" alpine /bin/sh -c "echo notify slack"

最後に

filter プラグインの grep を使ってもできると思います

参考サイト

2022年4月8日金曜日

docker で簡単に fluentd-plugin-slack を試す

docker で簡単に fluentd-plugin-slack を試す

概要

fluentd-plugin-slack は fluentd に来たログを slack に投げることができます
今回は docker で試してみました

環境

  • Ubuntu 18.04
  • docker 20.10.7

Dockerfile

  • vim Dockerfile
FROM fluent/fluentd

RUN apk add --update --virtual .build-deps sudo build-base ruby-dev \
 && sudo gem install fluent-plugin-slack \
 && sudo gem sources --clear-all \
 && apk del .build-deps \
 && rm -rf /var/cache/apk/* /home/fluent/.gem/ruby/2.5.0/cache/*.gem

イメージ作成

  • docker build -t my_fluentd .

fleunt.conf 作成

  • vim fluent.conf
<source>
  @type forward
  port 24224
  bind 0.0.0.0
</source>

<match docker.**>
  @type copy
  <store>
    @type stdout
  </store>
  <store>
    @type slack
    webhook_url https://hooks.slack.com/services/xxx/xxx/xxxxxx
    channel private
    username test
    message_keys log
    icon_emoji :ghost:
    flush_interval 60s
  </store>
</match>

message_keys で fluentd に流れてきた JSON ログのキーを指定することでその値を Slack のメッセージとして流すことができます

fluentd コンテナ起動

  • docker run -d -p 24224:24224 -p 24224:24224/udp -v $(pwd):/fluentd/etc -e FLUENTD_CONF=fluent.conf --name my_fluentd my_fluentd

動作確認

  • docker run --rm --log-driver=fluentd --log-opt fluentd-address=192.168.100.1:24224 --log-opt tag="docker.{{.Name}}" alpine /bin/sh -c "date"

今回は flush_interval 60s なので 1 分ごとにまとめて通知されます

特定のログだけ通知するには

grep プラグインと組み合わせます
こちらは次回紹介します

参考サイト

2022年4月7日木曜日

express のリクエストをクラスのオブジェクトにシリアライズするサンプルコード

express のリクエストをクラスのオブジェクトにシリアライズするサンプルコード

概要

TypeScript を使って型を定義しつつシリアライズします

環境

  • macOS 11.6.5
  • nodejs 17.8.0
  • express 4.17.3

サンプルコード

  • vim app.ts
import express from 'express'
const app = express()
const port = 3000

// ユーザプロファイルを管理するクラス
// 受け取ったリクエストはこのクラスのオブジェクトとして扱われる
// クラスはインタフェースを実装することで定義します 
// そうすることで定義したインタフェースを返り値などの型定義に使用できます
interface UserProfileType {
  name?: string,
  age?: string
}
class UserProfile implements UserProfileType {
  constructor(public name: string | undefined, public age: string | undefined) {
  }
  toJSON(): UserProfileType {
    return {
      name: this.name,
      age: this.age,
    }
  }
  // name と age が指定されているかの判定
  validate(): boolean {
    if (this.name === undefined || this.age === undefined) {
      return false
    } else {
      return true
    }
  }
  // レスポンス返却します
  show(res: express.Response): void {
    if (!this.validate()) {
      let errMsg = {'Error': 'You must specify name and age.'}
      res.send(JSON.stringify(errMsg))
    } else {
      res.send(JSON.stringify(this))
    }
  }
}

// express.Request を拡張してリクエストとして受け取るパラメータを再定義する
// 文字列または未定義のパラメータとして受け取る
interface UserProfileRequest extends express.Request {
  query: {
    name: string | undefined
    age: string | undefined
  }
}

// 受け取ったリクエストをそのままレスポンスとして返すルーティング
app.get('/', (req: UserProfileRequest, res: express.Response) => {
  let profile = new UserProfile(req.query.name, req.query.age)
  profile.show(res)
})

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`)
})

ポイント

UserProfileRequest インタフェースを定義して受け取るべきリクエストを定義しています
UserProfileRequest から UserProfile クラスのオブジェクトにシリアライズしていますが factory などを作って req から直接オブジェクトを生成してもいいかもしれません

クエリストリングは文字列でしか受け取れないので UserProfile.age も文字列で定義しています
本当は number として扱いたいので parseInt などを使ってどこかで変換してあげてもいいかもしれません

ちなみに undefined の部分はユニオンではなく Optional Property としても定義できます

interface UserProfileRequest extends express.Request {
  query: {
    name?: string
    age?: string
  }
}

あと toJSON の返り値の型も指定したかったので UserProfileType インタフェースを作成したあとで implements で UserProfile クラスを定義しています

最後に

今回は自力でシリアライズしましたが探せば便利なパッケージがあるかもしれません

2022年4月6日水曜日

express + TypeScript 超入門

express + TypeScript 超入門

概要

express のサンプルコードに TypeScript を適用してみました

環境

  • macOS 11.6.5
  • nodejs 17.8.0
  • express 4.17.3

インストール

  • npm install express --save-dev

サンプルアプリ

  • vim app.js
const express = require('express')
const app = express()
const port = 3000

app.get('/', (req, res) => {
  res.send('Hello World!')
})

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`)
})

動作確認

  • npm exec node app.js
  • curl localhost:3000

これを TypeScript 化してみます

TypeScript 化する

ts-node というパッケージを使うと簡単に TypeScript 化した express アプリを動かすことができます
TypeScript に必要なパッケージも合わせてインストールします

  • npm install ts-node typescript @types/node @types/express --save-dev

tsconfig.json も作成しておきましょう

  • npx tsc --init

とりあえずインタフェースを導入してみます
TypeScript を使うので拡張子を .ts にしましょう

  • vim app.ts
import express from 'express'
const app = express()
const port = 3000

interface MyResponse {
  message: string;
}

function createMessage(my_message: string): MyResponse {
  let myResponse = {
    message: my_message
  }
  return myResponse
}

app.get('/', (req: express.Request, res: express.Response) => {
  let msg = createMessage('Hello World!')
  res.send(msg.message)
})

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`)
})
  • npx ts-node app.ts
  • curl localhost:3000

少し解説

express のインポートを require から import 文に変更する必要があります
これは routing の req と res 引数に express.Request と express.Response を型定義するためです

createMessage は MyResponse インタフェースで返す必要があります

class を導入してみる

  • vim app.ts
import express from 'express'
const app = express()
const port = 3000

class UserProfile {
  constructor(public name: string, public age: number) {
  }
  toJSON = () => {
    return {
      name: this.name,
      age: this.age,
    }
  }
}

function show(profile: UserProfile, res: express.Response) {
  res.send(JSON.stringify(profile))
}

app.get('/', (req: express.Request, res: express.Response) => {
  let profile = new UserProfile("hawksnowlog", 10)
  show(profile, res)
})

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`)
})

UserProfile というクラスを定義しました
toJSON を実装しておくと JSON.stringify 時に自動で呼び出してくれるようです

最後に

express で扱うリクエストのオブジェクトはクラスなどで管理するとどんなリクエストが飛んでくるのかクラスの定義を見ただけでわかるようになるので良いかなと思います

レスポンスも同様でクラス化するとどんなレスポンスが返るのかわかりやすくなるかなと思います

express.Request をクラスのオブジェクトにシリアライズする簡単な方法もあるかもしれません

参考サイト

2022年4月5日火曜日

Gitlab の lint-doc.sh を実行する方法

Gitlab の lint-doc.sh を実行する方法

概要

インストール環境は過去に紹介したソースインストール環境の Gitlab を使います
scripts/lint-doc.sh は各種ドキュメントの表記ゆれや形式チェックを行ってくれます

環境

  • Ubuntu 18.04
  • markdownlint 0.31.1
  • vale 2.15.4

必要なツールのインストール

markdownlint

  • npm install -g markdownlint-cli

vale

  • apt -y install snapd
  • snap install vale --edge

実行

  • cd /home/git/gitlab
  • sudo -u git -H scripts/lint-doc.sh

参考サイト

2022年4月4日月曜日

Ubuntu18.04 に Gitlab のドキュメントをビルドできる環境を構築する

Ubuntu18.04 に Gitlab のドキュメントをビルドできる環境を構築する

概要

過去にも紹介しましたが最新バージョンで再度ドキュメントをビルドしてみました

環境

  • Ubuntu 18.04
  • Ruby 2.7.5
  • Nodejs v16.14.2
  • yarn 1.22.18

事前準備

  • apt -y install curl git

Ruby インストール

  • apt -y install autoconf bison build-essential libssl-dev libyaml-dev libreadline6-dev zlib1g-dev libncurses5-dev libffi-dev libgdbm5 libgdbm-dev libdb-dev
  • curl -fsSL https://github.com/rbenv/rbenv-installer/raw/HEAD/bin/rbenv-installer | bash
  • echo 'export RBENV_ROOT="$HOME/.rbenv"' >> ~/.bashrc
  • echo 'export PATH="$RBENV_ROOT/bin:$PATH"' >> ~/.bashrc
  • rbenv install 2.7.5
  • echo 'eval "$(rbenv init -)"' >> ~/.bashrc
  • rbenv global 2.7.5

nodejs インストール

  • curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
  • echo 'export NVM_DIR="$HOME/.nvm"' >> ~/.bashrc
  • echo '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm' >> ~/.bashrc
  • echo '[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion' >> ~/.bashrc
  • nvm install --lts
  • nvm use --lts --default

yarn インストール

  • npm install --global yarn

各種リポジトリのクーロン

  • mkdir -p ~/dev
  • cd ~/dev
  • git clone https://gitlab.com/gitlab-org/gitlab.git
  • git clone https://gitlab.com/gitlab-org/gitlab-runner.git
  • git clone https://gitlab.com/gitlab-org/omnibus-gitlab.git
  • git clone https://gitlab.com/gitlab-org/charts/gitlab.git charts-gitlab

gitlab-docs のクローン

  • cd ~/dev
  • git clone https://gitlab.com/gitlab-org/gitlab-docs.git
  • cd gitlab-docs
  • bundle install && yarn install --frozen-lockfile
  • ls -l
root@ubuntu:~/dev# ls -l
total 20
drwxr-xr-x 12 root root 4096 Apr  1 09:48 charts-gitlab
drwxr-xr-x 38 root root 4096 Apr  1 09:47 gitlab
drwxr-xr-x 15 root root 4096 Apr  1 09:19 gitlab-docs
drwxr-xr-x 24 root root 4096 Apr  1 09:48 gitlab-runner
drwxr-xr-x 15 root root 4096 Apr  1 09:48 omnibus-gitlab

動作確認

  • cd ~/dev/gitlab-docs
  • bundle exec nanoc live -o 0.0.0.0 -p 80

長いですがドキュメントの生成が始まります
ドキュメントの生成完了後に localhost:80 でブラウザにアクセスするとドキュメントを確認できます

error: unknown option 'show-current' というエラーログが出ることがありますが正常にドキュメントの生成は行われるようです

最後に

過去の手順と大きく変わってはいませんでした
サポートしている Ruby や nodejs のバージョンが少し違っているくらいかなと思います

参考サイト

2022年4月1日金曜日

ソースコードでインストールしたGitlabでrspecテストを実行する方法

ソースコードでインストールしたGitlabでrspecテストを実行する方法

概要

前回 Gitlab をソースコードインストールしました
今回はその環境を使ってrspecテストを実行する方法を紹介します

環境

  • Ubuntu 18.04
  • Gitlab 14-9-ee branch

テスト用の gem のインストール

  • sudo apt-get -y install libsqlite3-dev
  • vim /home/git/gitlab/.bundle/config
BUNDLE_WITHOUT: "mysql:aws:kerberos"

test を削除します

  • cd /home/gitlab/gitlab
  • sudo -u git -H bundle install

config/database.yml の編集

  • sudo -u git -H editor config/database.yml

各環境の username, password, host の部分をコメントアウトします

config/resque.yml の編集

  • sudo -u git -H editor config/resque.yml

各環境で url の部分を unix:/var/run/redis/redis.sock に変更します

テストの準備

  • sudo -u git -H bundle exec rake setup

これでテストに必要なデータが登録されます

テスト実行

  • sudo -u git -H bundle exec rspec spec/lib/backup/manager_spec.rb

こんな感じでテストファイルを指定するほうが良いです

  • sudo -u git -H bundle exec rspec

でフルテストできますがかなり時間がかかります

カバレッジの表示

  • export SIMPLECOV=1 && sudo -u git -E -H bundle exec rspec spec/lib/backup/manager_spec.rb

最後に

ソースコードでインストールした場合はテスト環境は自分で構築する必要があります

それが面倒な場合は gdk を使う感じになるかなと思います

参考サイト