SORACOM UG Explorer 2022 秋のハンズオン祭りに参加してきた

つい最近、転職した会社で思いがけずIoT関連の仕事に絡む事となり、SORACOMを使う機会に恵まれたので、SNS上でSORACOMデビューとつぶやいていたところ、それを見つけたかつての職場の同僚がSORACOM UGの運営メンバーだったらしく、以下のイベントを案内してくれた。

soracom-ug.jp

個人的にIoTの領域は未経験であり、SORACOM自体も昔の職場で同僚がCanalを使ってAWSとごにょごにょしていたのを傍目で見ていた程度でしか知らず、実機を使ってハンズオン出来る機会というのは願ったり叶ったりだった為、折角なので参加してみた。

SORACOM UG Explorer 2022は、全国5箇所の会場(札幌、東京、広島、高知、福岡)とオンラインで同時開催され、自分は東京で物理参加したのだが、東京会場はヤフーさんであり、紀尾井タワー移転後のオフィスは初めての訪問。

soracomug-tokyo.connpass.com

紀尾井タワーと言えば、コロナ前は通勤時間帯のオフィスエレベーターの渋滞が凄まじいという話題を耳にしており、そのくらいのイメージしか無かったのだが、この日はコロナ以降の休日という事もあって、当然ガラガラというか人気も殆ど無く、めちゃくちゃ広いわ静かだわ、お手洗いも綺麗だわで、眺望も素晴らしくて環境としては最高だった。

ハンズオン

各会場のUG支部による挨拶や紹介LT、会場案内、ハンズオンの概要説明等があった後、いよいよハンズオン開始。

今回は以下の3種類のデバイスを使って、SORACOMのサービスを体験する事が出来るといった内容。

バイスはSORACOMさんがレンタルしてくれる為、事前に自ら購入して持ち込む必要も無く、予めSORACOMアカウントだけを準備しておけばOKだった。

また、SIMについては特定地域向け IoT SIM (plan-D)サイズ:ナノ(データ通信のみ/D-300MB)を1人につき1枚無償提供頂き、そのまま持ち帰って私物として使わせてもらえる事に(ありがたやありがたや)。

GPSマルチユニット

自分はGPSマルチユニットでのハンズオンを予約していたので、まず最初はSORACOM IoT DIY レシピで公開されている以下のレシピを実践する。

soracom.jp

概要を説明すると、SORACOM AirのSIMを挿したIoTデバイスのセンサーから、温度や湿度といったデータを収集してHarvest Dataに蓄積し、それらをGrafanaベースのLagoonで可視化するといった内容。

SIMの登録を済ませて以降は、基本的にレシピの手順通りになぞるだけだったので、難しい操作であったり、詰まってしまう様なところも特に無く、あっさりと終わってしまった。

ハンズオンとしては物足りなさを感じたものの、それくらい殆ど手間をかけずにサンプルにある様なLagoonダッシュボードを構築して、IoTデバイスの状態を可視化出来てしまうというのは、利便性を感じさせてくれるポイントである。

IoTの文脈で言えば、ここから更にデータを分析して活用する、といったサイクルを築いていく必要がある訳だが、そのIoTの取っ掛かりを部分体験するといった意味で、自分にとっては最初の一歩として丁度良い経験になったと思う。

白ボタン

思いの外、時間がたっぷりと余ってしまった為、次に白ボタンを使ったハンズオンにも取り組んでみた。

soracom.github.io

白ボタンはレンタル持ち込み仮想ボタンを使うケースの3パターンが用意されており、レンタルしても良かったのだが、SORACOM Arcを使ってみたかったので、仮想ボタンのケースを選択する。

このハンズオンの概要は、SORACOM Arcによって仮想ボタンから取得したボタン操作のデータをHarvest Dataに蓄積しつつ、もう一方でOrbitを使いWebAssemblyによってボタン操作に応じた出力データを変換し、BeamでLINE Notifyに転送するといった内容になっているのだが、SORACOM Arcについては仮想ボタンのケースでのみ使用する。

GPSマルチユニットのハンズオンとは異なり、それなりのボリュームもありつつ、デバイスから収集したデータを活用するところまで体験出来る為、個人的にはこちらの方がIoTやってる感があったのと、SIMも実機も無くてもOKというところで、WireGuard含めその仕組みが興味深くて面白かった。

ソラカメ

流石に時間が足りなくなってしまい、こちらまでは取り組めなかったのだが、ソラカメを使ったハンズオンでは、SORACOM CLIAPIを使って複数の連続した静止画像からタイムラプス動画を作っていた模様。

soracom.github.io

ATOM Camがお手頃価格なので、自分でも購入して遊んでみたくなった。

LT

SORACOMの中の人達のLTと、一般公募の方々のLTと、色々と面白い発表があったのだが、自分が捕捉出来たスライドはわずかだった(そのうちに、上にもリンクしているConnpassにまとめられるかも…しれない)。

オフィスの課題をIoTで解決。

母の愛を感じるIoT事例。

いずれも身近の課題をしっかりIoTで解決されたというお話で、素人としては感心すると同時に、自分も小さな事からIoTで何かの形を成してみたいなと思わせる、チャレンジ意欲の湧いてくる様なLTでした。

感想

コロナ前から随分と表のイベントには参加しなくなっていたのだが、ひょんな切っ掛けから久々にこういったコミュニティに参加してみて、何やら懐かしさを感じつつ、思っていた以上に楽しむ事が出来た。

人数的には参加者より運営の方が多かったので、最初はどうしても身内ノリ的な感じを受けてしまうところもありはしたものの、コミュニティとして全体の雰囲気は悪くなく、適度なゆるさも相俟ってか、入り辛さみたいな空気はあまり無かった様に思う。

学びたかった事はしっかり学べ、ハンズオン中の雑談やLTでは新たな知見も得る事が出来たし、最終的にはエバンジェリストMaxさんを始めとして、参加者の皆さんとも大いに盛り上がったので、参加して良かったなというのが正直な感想。

運営の皆さん、SORACOMさん、会場のヤフーさん、そして何より、誘ってくれた元同僚に感謝。

これから

趣味レベルで小さな事からIoTを実践してみたり、業務に関連してAWSとSORACOMを連携させたIoTのインフラアーキテクチャであったり、ネットワークやデバイス監視、セキュリティ、また、AWS IoT等についても学習していきたいので、今後もそういった分野が学べるイベントや機会があれば、積極的に参加していきたい。

M1 Macbook ProでRubyGemsのeventmachineをインストールした際に吐かれたSSL関連のビルドエラーについて

仕事で8年ぶりくらいにRubyに触れる機会があり、M1 Macbook ProRailsの環境を構築中、RubyGemsのeventmachineをインストールしたらSSL関連のビルドエラーが生じたので、その問題解消までのメモを残しておく。

環境

  • M1 Macbook Pro (14インチ、2021)
  • macOS Monterey バージョン12.6
  • anyenv 1.1.5
  • rbenv 1.2.0-33-ga6cf6ae
  • Ruby 2.7.5
  • RubyGems 3.1.6
  • eventmachine 1.2.7

エラー内容

$ gem install eventmachine
Building native extensions. This could take a while...
ERROR:  Error installing eventmachine:
    ERROR: Failed to build gem native extension.

    current directory: /Users/heroween/.anyenv/envs/rbenv/versions/2.7.5/lib/ruby/gems/2.7.0/gems/eventmachine-1.2.7/ext
/Users/heroween/.anyenv/envs/rbenv/versions/2.7.5/bin/ruby -I /Users/heroween/.anyenv/envs/rbenv/versions/2.7.5/lib/ruby/2.7.0 -r ./siteconf20221021-64873-1gud8b.rb extconf.rb
checking for -lcrypto... no
checking for openssl/ssl.h... yes
checking for openssl/err.h... yes
checking for rb_trap_immediate in ruby.h,rubysig.h... no
checking for rb_thread_blocking_region()... no
checking for rb_thread_call_without_gvl() in ruby/thread.h... yes
checking for rb_thread_fd_select()... yes
checking for rb_fdset_t in ruby/intern.h... yes
checking for rb_wait_for_single_fd()... yes
checking for rb_enable_interrupt()... no
checking for rb_time_new()... yes
checking for inotify_init() in sys/inotify.h... no
checking for __NR_inotify_init in sys/syscall.h... no
checking for writev() in sys/uio.h... yes
checking for pipe2() in unistd.h... no
checking for accept4() in sys/socket.h... no
checking for SOCK_CLOEXEC in sys/socket.h... no
checking for sys/event.h... yes
checking for sys/queue.h... yes
checking for clock_gettime()... yes
checking for CLOCK_MONOTONIC_RAW in time.h... yes
checking for CLOCK_MONOTONIC in time.h... yes
CXXFLAGS=-g -O2 -Wall -Wextra -Wno-deprecated-declarations -Wno-ignored-qualifiers -Wno-unused-result -Wno-address
creating Makefile

current directory: /Users/heroween/.anyenv/envs/rbenv/versions/2.7.5/lib/ruby/gems/2.7.0/gems/eventmachine-1.2.7/ext
make "DESTDIR=" clean

current directory: /Users/heroween/.anyenv/envs/rbenv/versions/2.7.5/lib/ruby/gems/2.7.0/gems/eventmachine-1.2.7/ext
make "DESTDIR="
compiling binder.cpp
compiling cmain.cpp
compiling ed.cpp
compiling em.cpp
compiling kb.cpp
compiling page.cpp
compiling pipe.cpp
compiling rubymain.cpp
rubymain.cpp:220:3: warning: 'rb_rescue' is deprecated: Use of ANYARGS in this function is deprecated [-Wdeprecated-declarations]
                rb_rescue((VALUE (*)(ANYARGS))event_callback, (VALUE)&e, (VALUE (*)(ANYARGS))event_error_handler, Qnil);
                ^
/Users/heroween/.anyenv/envs/rbenv/versions/2.7.5/include/ruby-2.7.0/ruby/backward/cxxanyargs.hpp:184:1: note: 'rb_rescue' has been explicitly marked deprecated here
RUBY_CXX_DEPRECATED("Use of ANYARGS in this function is deprecated")
^
/Users/heroween/.anyenv/envs/rbenv/versions/2.7.5/include/ruby-2.7.0/arm64-darwin21/ruby/config.h:152:49: note: expanded from macro 'RUBY_CXX_DEPRECATED'
#define RUBY_CXX_DEPRECATED(msg) __attribute__((__deprecated__(msg)))
                                                ^
1 warning generated.
compiling ssl.cpp
linking shared-object rubyeventmachine.bundle
Undefined symbols for architecture arm64:
  "_BIO_ctrl", referenced from:
      t_get_peer_cert(unsigned long, unsigned long) in rubymain.o
      _ssl_verify_wrapper in ssl.o
      SslBox_t::CanGetCiphertext() in ssl.o
      SslBox_t::PutPlaintext(char const*, int) in ssl.o
  "_BIO_free", referenced from:
      t_get_peer_cert(unsigned long, unsigned long) in rubymain.o
      SslContext_t::SslContext_t(bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int) in ssl.o
      _ssl_verify_wrapper in ssl.o
  "_BIO_new", referenced from:
      t_get_peer_cert(unsigned long, unsigned long) in rubymain.o
      SslBox_t::SslBox_t(bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, bool, bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int, unsigned long) in ssl.o
      _ssl_verify_wrapper in ssl.o
  "_BIO_new_file", referenced from:
      SslContext_t::SslContext_t(bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int) in ssl.o
  "_BIO_new_mem_buf", referenced from:
      SslContext_t::SslContext_t(bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int) in ssl.o
  "_BIO_read", referenced from:
      SslBox_t::GetCiphertext(char*, int) in ssl.o
  "_BIO_s_mem", referenced from:
      t_get_peer_cert(unsigned long, unsigned long) in rubymain.o
      SslBox_t::SslBox_t(bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, bool, bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int, unsigned long) in ssl.o
      _ssl_verify_wrapper in ssl.o
  "_BIO_write", referenced from:
      _ssl_verify_wrapper in ssl.o
      SslBox_t::PutCiphertext(char const*, int) in ssl.o
  "_DH_free", referenced from:
      SslContext_t::SslContext_t(bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int) in ssl.o
  "_EC_KEY_free", referenced from:
      SslContext_t::SslContext_t(bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int) in ssl.o
  "_EC_KEY_new_by_curve_name", referenced from:
      SslContext_t::SslContext_t(bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int) in ssl.o
  "_ERR_print_errors_fp", referenced from:
      SslBox_t::SslBox_t(bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, bool, bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int, unsigned long) in ssl.o
      SslBox_t::GetPlaintext(char*, int) in ssl.o
      SslContext_t::SslContext_t(bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int) (.cold.2) in ssl.o
      SslContext_t::SslContext_t(bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int) (.cold.3) in ssl.o
      SslContext_t::SslContext_t(bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int) (.cold.4) in ssl.o
      SslContext_t::SslContext_t(bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int) (.cold.5) in ssl.o
  "_EVP_PKEY_free", referenced from:
      SslContext_t::SslContext_t(bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int) in ssl.o
      SslContext_t::~SslContext_t() in ssl.o
      SslContext_t::~SslContext_t() in ssl.o
      SslContext_t::~SslContext_t() in ssl.o
  "_OBJ_sn2nid", referenced from:
      SslContext_t::SslContext_t(bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int) in ssl.o
  "_OPENSSL_init_crypto", referenced from:
      SslContext_t::SslContext_t(bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int) in ssl.o
  "_OPENSSL_init_ssl", referenced from:
      SslContext_t::SslContext_t(bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int) in ssl.o
  "_PEM_read_bio_DHparams", referenced from:
      SslContext_t::SslContext_t(bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int) in ssl.o
  "_PEM_read_bio_PrivateKey", referenced from:
      SslContext_t::SslContext_t(bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int) in ssl.o
  "_PEM_read_bio_X509", referenced from:
      SslContext_t::SslContext_t(bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int) in ssl.o
  "_PEM_write_bio_X509", referenced from:
      t_get_peer_cert(unsigned long, unsigned long) in rubymain.o
      _ssl_verify_wrapper in ssl.o
  "_SSL_CIPHER_get_bits", referenced from:
      SslBox_t::GetCipherBits() in ssl.o
  "_SSL_CIPHER_get_name", referenced from:
      SslBox_t::GetCipherName() in ssl.o
  "_SSL_CIPHER_get_version", referenced from:
      SslBox_t::GetCipherProtocol() in ssl.o
  "_SSL_CTX_ctrl", referenced from:
      SslContext_t::SslContext_t(bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int) in ssl.o
  "_SSL_CTX_free", referenced from:
      SslContext_t::~SslContext_t() in ssl.o
      SslContext_t::~SslContext_t() in ssl.o
      SslContext_t::~SslContext_t() in ssl.o
  "_SSL_CTX_new", referenced from:
      SslContext_t::SslContext_t(bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int) in ssl.o
  "_SSL_CTX_set_cipher_list", referenced from:
      SslContext_t::SslContext_t(bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int) in ssl.o
  "_SSL_CTX_set_options", referenced from:
      SslContext_t::SslContext_t(bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int) in ssl.o
  "_SSL_CTX_set_session_id_context", referenced from:
      SslContext_t::SslContext_t(bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int) in ssl.o
  "_SSL_CTX_use_PrivateKey", referenced from:
      SslContext_t::SslContext_t(bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int) in ssl.o
  "_SSL_CTX_use_PrivateKey_file", referenced from:
      SslContext_t::SslContext_t(bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int) in ssl.o
  "_SSL_CTX_use_certificate", referenced from:
      SslContext_t::SslContext_t(bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int) in ssl.o
  "_SSL_CTX_use_certificate_chain_file", referenced from:
      SslContext_t::SslContext_t(bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int) in ssl.o
  "_SSL_accept", referenced from:
      SslBox_t::GetPlaintext(char*, int) in ssl.o
  "_SSL_clear", referenced from:
      SslBox_t::~SslBox_t() in ssl.o
  "_SSL_connect", referenced from:
      SslBox_t::SslBox_t(bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, bool, bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int, unsigned long) in ssl.o
      SslBox_t::GetPlaintext(char*, int) in ssl.o
  "_SSL_ctrl", referenced from:
      SslBox_t::SslBox_t(bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, bool, bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int, unsigned long) in ssl.o
  "_SSL_free", referenced from:
      SslBox_t::~SslBox_t() in ssl.o
  "_SSL_get_current_cipher", referenced from:
      SslBox_t::GetCipherBits() in ssl.o
      SslBox_t::GetCipherName() in ssl.o
      SslBox_t::GetCipherProtocol() in ssl.o
  "_SSL_get_error", referenced from:
      SslBox_t::GetPlaintext(char*, int) in ssl.o
      SslBox_t::PutPlaintext(char const*, int) in ssl.o
  "_SSL_get_ex_data", referenced from:
      _ssl_verify_wrapper in ssl.o
  "_SSL_get_ex_data_X509_STORE_CTX_idx", referenced from:
      _ssl_verify_wrapper in ssl.o
  "_SSL_get_peer_certificate", referenced from:
      SslBox_t::GetPeerCert() in ssl.o
  "_SSL_get_servername", referenced from:
      SslBox_t::GetSNIHostname() in ssl.o
  "_SSL_get_shutdown", referenced from:
      SslBox_t::~SslBox_t() in ssl.o
  "_SSL_is_init_finished", referenced from:
      SslBox_t::GetPlaintext(char*, int) in ssl.o
      SslBox_t::PutPlaintext(char const*, int) in ssl.o
  "_SSL_new", referenced from:
      SslBox_t::SslBox_t(bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, bool, bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int, unsigned long) in ssl.o
  "_SSL_read", referenced from:
      SslBox_t::GetPlaintext(char*, int) in ssl.o
  "_SSL_set_bio", referenced from:
      SslBox_t::SslBox_t(bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, bool, bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int, unsigned long) in ssl.o
  "_SSL_set_ex_data", referenced from:
      SslBox_t::SslBox_t(bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, bool, bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int, unsigned long) in ssl.o
  "_SSL_set_verify", referenced from:
      SslBox_t::SslBox_t(bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, bool, bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int, unsigned long) in ssl.o
  "_SSL_shutdown", referenced from:
      SslBox_t::~SslBox_t() in ssl.o
  "_SSL_write", referenced from:
      SslBox_t::PutPlaintext(char const*, int) in ssl.o
  "_TLS_client_method", referenced from:
      SslContext_t::SslContext_t(bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int) in ssl.o
  "_TLS_server_method", referenced from:
      SslContext_t::SslContext_t(bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int) in ssl.o
  "_X509_STORE_CTX_get_current_cert", referenced from:
      _ssl_verify_wrapper in ssl.o
  "_X509_STORE_CTX_get_ex_data", referenced from:
      _ssl_verify_wrapper in ssl.o
  "_X509_free", referenced from:
      t_get_peer_cert(unsigned long, unsigned long) in rubymain.o
      SslContext_t::SslContext_t(bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int) in ssl.o
      SslContext_t::~SslContext_t() in ssl.o
      SslContext_t::~SslContext_t() in ssl.o
      SslContext_t::~SslContext_t() in ssl.o
ld: symbol(s) not found for architecture arm64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make: *** [rubyeventmachine.bundle] Error 1

make failed, exit code 2

Gem files will remain installed in /Users/heroween/.anyenv/envs/rbenv/versions/2.7.5/lib/ruby/gems/2.7.0/gems/eventmachine-1.2.7 for inspection.
Results logged to /Users/heroween/.anyenv/envs/rbenv/versions/2.7.5/lib/ruby/gems/2.7.0/extensions/arm64-darwin-21/2.7.0/eventmachine-1.2.7/gem_make.out

上記エラーメッセージから一部を抜粋すると、以下の様にssl.cppのコンパイルで落ちている事が分かる。

compiling ssl.cpp
linking shared-object rubyeventmachine.bundle
Undefined symbols for architecture arm64:

〜〜〜 中略 〜〜〜

ld: symbol(s) not found for architecture arm64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make: *** [rubyeventmachine.bundle] Error 1

make failed, exit code 2

ここから、リンカがSSLライブラリを参照出来ていないのではないかと問題のあたりをつけて調べてみた。

調査

rbenvではRubyが依存しているOpenSSLを、Rubyのバージョン毎に個別にインストールしているのだが(昔はHomebrewのOpenSSLに依存していた)、自分の環境だとanyenvとrbenvを使っているので、バージョン2.7.5が依存しているOpenSSLは以下のディレクトリに入っている。

~/.anyenv/envs/rbenv/versions/2.7.5/openssl

そして、RubyGemsでインストールしたeventmachineのソースは、以下のディレクトリに保存される。

~/.anyenv/envs/rbenv/versions/2.7.5/lib/ruby/gems/2.7.0/gems/eventmachine-1.2.7/ext

この中にextconf.rbで生成されたMakefileが入っているので、定義されているリンカオプションを確認してみたのだが、LDFLAGSにopenssl/libのパスは見当たらなかった。

ldflags  = -L. -L/Users/heroween/.anyenv/envs/rbenv/versions/2.7.5/lib  -fstack-protector-strong

これが原因ではないかと推察し、extconf.rbのコードから環境変数「PKG_CONFIG_PATH」が使える感じだったので、ビルド時にHomebrewでインストールしたOpenSSLのpcファイルを参照させてみる事に。

問題解消

MacではmacOS High Sierraから、OpenSSLのハートブリード問題を機に標準のSSLライブラリがLibreSSL(OpenSSL1.0.1からフォークされた)へと代わっており、これを書いている時点ではバージョン2.8.3がインストールされている。

$ openssl version
LibreSSL 2.8.3

しかし、Rubyが依存しているのはOpenSSLとなる為、まずはHomebrewを使ってOpenSSLをインストールする。

$ brew install openssl@1.1

ここでバージョン指定しないと現行の最新版であるバージョン3がインストールされるのだが、Ruby 2.7系はOpenSSLのバージョン3に対応していないので、バージョン1.1の方が必要となる。

インストールしたら、環境変数「PKG_CONFIG_PATH」にOpenSSL@1.1のpcファイルのパスを設定するのだが、brew info opsnssl@1.1コマンドを実行すると、以下の様に環境変数設定のコマンド実行例が出力される。

$ brew info openssl@1.1
==> openssl@1.1: stable 1.1.1q (bottled) [keg-only]
Cryptography and SSL/TLS Toolkit
https://openssl.org/
/opt/homebrew/Cellar/openssl@1.1/1.1.1q (8,097 files, 18MB)
  Poured from bottle on 2022-10-04 at 15:51:35
From: https://github.com/Homebrew/homebrew-core/blob/HEAD/Formula/openssl@1.1.rb
License: OpenSSL
==> Dependencies
Required: ca-certificates ✔
==> Caveats
A CA file has been bootstrapped using certificates from the system
keychain. To add additional certificates, place .pem files in
  /opt/homebrew/etc/openssl@1.1/certs

and run
  /opt/homebrew/opt/openssl@1.1/bin/c_rehash

openssl@1.1 is keg-only, which means it was not symlinked into /opt/homebrew,
because macOS provides LibreSSL.

If you need to have openssl@1.1 first in your PATH, run:
  echo 'export PATH="/opt/homebrew/opt/openssl@1.1/bin:$PATH"' >> ~/.zshrc

For compilers to find openssl@1.1 you may need to set:
  export LDFLAGS="-L/opt/homebrew/opt/openssl@1.1/lib"
  export CPPFLAGS="-I/opt/homebrew/opt/openssl@1.1/include"

For pkg-config to find openssl@1.1 you may need to set:
  export PKG_CONFIG_PATH="/opt/homebrew/opt/openssl@1.1/lib/pkgconfig"

==> Analytics
install: 712,643 (30 days), 2,287,873 (90 days), 11,108,325 (365 days)
install-on-request: 26,710 (30 days), 82,513 (90 days), 390,982 (365 days)
build-error: 1,533 (30 days)

上記メッセージの通りにコマンドを実行。

$ export PKG_CONFIG_PATH="/opt/homebrew/opt/openssl@1.1/lib/pkgconfig"

その上で、gem install eventmachineコマンドを再度実行してみると、今度は問題無くインストールが完了した。

改めて、生成されたMakefileに定義されるLDFLAGSを確認してみたところ、以下の様にopenssl/libのパスが追加されている事が分かる。

ldflags  = -L. -L/Users/heroween/.anyenv/envs/rbenv/versions/2.7.5/lib  -fstack-protector-strong -L/opt/homebrew/Cellar/openssl@1.1/1.1.1q/lib

という訳で、結論としては、やはりリンカがSSLライブラリを参照出来ていなかったというのが、ビルドエラーの根本原因だったと思われる。

おまけ

rbenvでは、Rubyインストール時のオプションによって、バンドルされたOpenSSLを使うのではなく、HomebrewのOpenSSLを使う事も可能となっている模様。

$ export RUBY_CONFIGURE_OPTS="--with-openssl-dir=$(brew --prefix openssl@1.1)"

この環境変数を設定してrbenv installコマンドを実行した場合は、Rubyと一緒にOpenSSLはインストールされなくなり、最初からHomebrewのOpenSSLに依存する様になる。

この形でインストールしていれば、今回のビルドエラーを回避出来たかもしれないのだが、取り敢えず問題は解消しているので、その検証自体はまた後日にでもやろうと思う。

M2 Macbook AirにanyenvとpyenvでAnacondaをインストールする

手持ちのM2 Macbook AirAnacondaが必要となり、anyenvpyenvでインストールしてみたので手順を残しておく。

anyenvのインストール

まずはHomebrewで様々な**envコマンドをまとめて管理する為のanyenvをインストールする。

$ brew install anyenv

anyenvがインストールされた事を確認。

$ anynev -v
anyenv 1.1.5

インストール出来たらanyenv initコマンドを実行し、デフォルトシェルの種類に応じたanyenvの設定コマンドを確認する。

$ anyenv init
# Load anyenv automatically by adding
# the following to ~/.config/fish/config.fish:

status --is-interactive; and source (anyenv init -|psub)

anyenvはbashzshksh、fishをサポートしているので、それぞれのシェルに合わせたメッセージが出力されるのだが、自分の環境はfishを使っているので、上述のメッセージに従ってconfig.fishに出力されたコマンドを追記してやる。

$ vi ~/.config/fish/config.fish
以下を追記
status --is-interactive; and source (anyenv init -|psub)

追記が済んだらシェルをリロード。

$ exec $SHELL -l

次に、**envコマンドをインストールする為のanyenv-installプラグインを設定する。

$ anyenv install --init
Manifest directory doesn't exist: /Users/heroween/.config/anyenv/anyenv-install
Do you want to checkout https://github.com/anyenv/anyenv-install.git? [y/N]: y
Cloning https://github.com/anyenv/anyenv-install.git master to /Users/heroween/.config/anyenv/anyenv-install...
Cloning into '/Users/heroween/.config/anyenv/anyenv-install'...
remote: Enumerating objects: 71, done.
remote: Counting objects: 100% (14/14), done.
remote: Compressing objects: 100% (13/13), done.
remote: Total 71 (delta 4), reused 3 (delta 1), pack-reused 57
Receiving objects: 100% (71/71), 13.15 KiB | 6.58 MiB/s, done.
Resolving deltas: 100% (11/11), done.

Completed!

anyenv install -lコマンドでインストール可能な**envコマンドのリストが表示されたらanyenvの設定は完了。

$ ~/.config anyenv install -l
  Renv
  crenv
  denv
  erlenv
  exenv
  goenv
  hsenv
  jenv
  jlenv
  kubectlenv
  luaenv
  nodenv
  phpenv
  plenv
  pyenv
  rbenv
  sbtenv
  scalaenv
  swiftenv
  tfenv

pyenvのインストール

anyenvの設定が完了したら、複数バージョンのPythonを共存可能にするpyenvをインストールする。

$ anyenv install pyenv
/var/folders/0b/7w0c1g251msb55zpbzt6ls9r0000gn/T/pyenv.20221015230118.98707 ~
Cloning https://github.com/pyenv/pyenv.git master to pyenv...
Cloning into 'pyenv'...
remote: Enumerating objects: 22020, done.
remote: Counting objects: 100% (88/88), done.
remote: Compressing objects: 100% (57/57), done.
remote: Total 22020 (delta 37), reused 69 (delta 24), pack-reused 21932
Receiving objects: 100% (22020/22020), 4.43 MiB | 990.00 KiB/s, done.
Resolving deltas: 100% (14897/14897), done.
~

Install pyenv succeeded!
Please reload your profile (exec $SHELL -l) or open a new session.

インストールが完了したらシェルをリロード。

$ exec $SHELL -l

pyenvがインストールされた事を確認。

$ pyenv --version
pyenv 2.3.5-2-g03a5d653

pyenvの実体は以下のディレクトリにインストールされている。

$ pyenv root
/Users/heroween/.anyenv/envs/pyenv

ここまでで、同一環境に複数バージョンのPythonを共存させる事が出来るようになったものの、同じバージョンの環境を複数構成するといった事は出来ない為、それを実現するpyenvのプラグインとなるpyenv-virtualenvもインストールする。

$ git clone https://github.com/pyenv/pyenv-virtualenv.git $(pyenv root)/plugins/pyenv-virtualenv

インストールが完了したら再びシェルをリロード。

$ exec $SHELL -l

pyenv-virtualenvのインストールを確認する。

$ pyenv virtualenv --version
pyenv-virtualenv 1.1.5 (virtualenv unknown)

これでpyenvの設定は完了。

Anacondaをインストールする

pyenvを使って本来の目的となるAnacondaをインストールする。

pyenv install -lコマンドを実行すると、インストール可能なPython2、Python3や、Anaconda、Miniconda(Anacondaのmininal版)等の各バージョンがリスト表示されるので、そこからインストールしたいAnacondaのバージョンを取得する。

$ pyenv install -l
Available versions:
  2.1.3
  2.2.3
  2.3.7
~~~~~ 中略 ~~~~~
  anaconda3-2020.11
  anaconda3-2021.04
  anaconda3-2021.05
  anaconda3-2021.11
  anaconda3-2022.05

執筆時の最新版となるanaconda3-2022.05をインストールする場合、以下のコマンドを実行する(もっとも、anaconda3-2022.05以前のバージョンはMacOSX-arm64に未対応なので、そもそもM2 Macbook Airにはインストール出来ないのだけども)。

$ pyenv install anaconda3-2022.05
Downloading Anaconda3-2022.05-MacOSX-arm64.sh...
-> https://repo.anaconda.com/archive/Anaconda3-2022.05-MacOSX-arm64.sh
Installing Anaconda3-2022.05-MacOSX-arm64...
Installed Anaconda3-2022.05-MacOSX-arm64 to /Users/heroween/.anyenv/envs/pyenv/versions/anaconda3-2022.05

インストールバージョンを確認。

$ pyenv versions
* system (set by /Users/heroween/.anyenv/envs/pyenv/version)
  anaconda3-2022.05

GlobalのPython環境をanaconda3-2022.05に変更。

$ pyenv global anaconda3-2022.05
$ pyenv versions
  system
* anaconda3-2022.05 (set by /Users/heroween/.anyenv/envs/pyenv/version)

condaコマンドが機能する事を確認する。

$ conda -V
conda 4.13.0
$ conda env list
# conda environments:
#
base                  *  /Users/heroween/.anyenv/envs/pyenv/versions/anaconda3-2022.05

次に、デフォルトシェルにAnacondaの初期化設定をしてやる。

$ conda init fish
no change     /Users/heroween/.anyenv/envs/pyenv/versions/anaconda3-2022.05/condabin/conda
no change     /Users/heroween/.anyenv/envs/pyenv/versions/anaconda3-2022.05/bin/conda
no change     /Users/heroween/.anyenv/envs/pyenv/versions/anaconda3-2022.05/bin/conda-env
no change     /Users/heroween/.anyenv/envs/pyenv/versions/anaconda3-2022.05/bin/activate
no change     /Users/heroween/.anyenv/envs/pyenv/versions/anaconda3-2022.05/bin/deactivate
no change     /Users/heroween/.anyenv/envs/pyenv/versions/anaconda3-2022.05/etc/profile.d/conda.sh
no change     /Users/heroween/.anyenv/envs/pyenv/versions/anaconda3-2022.05/etc/fish/conf.d/conda.fish
no change     /Users/heroween/.anyenv/envs/pyenv/versions/anaconda3-2022.05/shell/condabin/Conda.psm1
no change     /Users/heroween/.anyenv/envs/pyenv/versions/anaconda3-2022.05/shell/condabin/conda-hook.ps1
no change     /Users/heroween/.anyenv/envs/pyenv/versions/anaconda3-2022.05/lib/python3.9/site-packages/xontrib/conda.xsh
no change     /Users/heroween/.anyenv/envs/pyenv/versions/anaconda3-2022.05/etc/profile.d/conda.csh
modified      /Users/heroween/.config/fish/config.fish

==> For changes to take effect, close and re-open your current shell. <==

すると、fishのconfig.fishファイルに以下の様なAnacondaの設定が追記され、シェルの起動時に自動でAnacondaがactivateされる様になる。

# >>> conda initialize >>>
# !! Contents within this block are managed by 'conda init' !!
eval /Users/heroween/.anyenv/envs/pyenv/versions/anaconda3-2022.05/bin/conda "shell.fish" "hook" $argv | source
# <<< conda initialize <<<

なお、conda init --allを実行すると、Anacondaがサポートしている全てのシェル(bashzshtcsh、fish、xonsh、powershell)に設定が追記されるので、複数シェルでまとめて設定したい場合は--allオプションを使うと便利。

完了したらシェルをリロード。

$ exec $SHELL -l

これでAnacondaのインストールまでが完了となる。

Anacondaの仮想環境を作成してみる

pyenv-virtualenvを使ってAnacondaの仮想環境を作るには、以下のコマンドを実行する。

$ pyenv virtualenv anaconda3-2022.05 myenv
Collecting package metadata (current_repodata.json): done
Solving environment: done


==> WARNING: A newer version of conda exists. <==
  current version: 4.13.0
  latest version: 22.9.0

Please update conda by running

    $ conda update -n base -c defaults conda



## Package Plan ##

  environment location: /Users/heroween/.anyenv/envs/pyenv/versions/anaconda3-2022.05/envs/myenv

  added / updated specs:
    - python


The following NEW packages will be INSTALLED:

  bzip2              pkgs/main/osx-arm64::bzip2-1.0.8-h620ffc9_4
  ca-certificates    pkgs/main/osx-arm64::ca-certificates-2022.07.19-hca03da5_0
  certifi            pkgs/main/osx-arm64::certifi-2022.9.24-py310hca03da5_0
  libcxx             pkgs/main/osx-arm64::libcxx-14.0.6-h848a8c0_0
  libffi             pkgs/main/osx-arm64::libffi-3.4.2-hc377ac9_4
  ncurses            pkgs/main/osx-arm64::ncurses-6.3-h1a28f6b_3
  openssl            pkgs/main/osx-arm64::openssl-1.1.1q-h1a28f6b_0
  pip                pkgs/main/osx-arm64::pip-22.2.2-py310hca03da5_0
  python             pkgs/main/osx-arm64::python-3.10.6-hbdb9e5c_0
  readline           pkgs/main/osx-arm64::readline-8.1.2-h1a28f6b_1
  setuptools         pkgs/main/osx-arm64::setuptools-63.4.1-py310hca03da5_0
  sqlite             pkgs/main/osx-arm64::sqlite-3.39.3-h1058600_0
  tk                 pkgs/main/osx-arm64::tk-8.6.12-hb8d0fd4_0
  tzdata             pkgs/main/noarch::tzdata-2022c-h04d1e81_0
  wheel              pkgs/main/noarch::wheel-0.37.1-pyhd3eb1b0_0
  xz                 pkgs/main/osx-arm64::xz-5.2.6-h1a28f6b_0
  zlib               pkgs/main/osx-arm64::zlib-1.2.12-h5a0b063_3


Preparing transaction: done
Verifying transaction: done
Executing transaction: done
#
# To activate this environment, use
#
#     $ conda activate myenv
#
# To deactivate an active environment, use
#
#     $ conda deactivate

Looking in links: /var/folders/0b/7w0c1g251msb55zpbzt6ls9r0000gn/T/tmp131_n1rs
Requirement already satisfied: setuptools in /Users/heroween/.anyenv/envs/pyenv/versions/anaconda3-2022.05/envs/myenv/lib/python3.10/site-packages (63.4.1)
Requirement already satisfied: pip in /Users/heroween/.anyenv/envs/pyenv/versions/anaconda3-2022.05/envs/myenv/lib/python3.10/site-packages (22.2.2)

pyenv virtualenvコマンドを実行すると仮想環境が作成される訳だが、実行環境にcondaコマンドが存在している場合、内部的にはconda createが使われる様になっている。

$ pyenv versions
* system (set by /Users/heroween/.anyenv/envs/pyenv/version)
  anaconda3-2022.05
  anaconda3-2022.05/envs/myenv
  myenv
$ conda env list
# conda environments:
#
base                  *  /Users/heroween/.anyenv/envs/pyenv/versions/anaconda3-2022.05
myenv                    /Users/heroween/.anyenv/envs/pyenv/versions/anaconda3-2022.05/envs/myenv

新しく作成したAnacondaの仮想環境へ切り替えるには、conda activateコマンドを実行。

$ conda activate myenv
$ conda env list
# conda environments:
#
base                     /Users/heroween/.anyenv/envs/pyenv/versions/anaconda3-2022.05
myenv                 *  /Users/heroween/.anyenv/envs/pyenv/versions/anaconda3-2022.05/envs/myenv
$ pyenv versions
* system (set by /Users/heroween/.anyenv/envs/pyenv/version)
  anaconda3-2022.05
  anaconda3-2022.05/envs/myenv
  myenv

あくまでAnaconda内における仮想環境の切り替えとなる為、pyenv側が切り替わることは無い。

pyenv側で切り替えるのであれば、一旦、conda deactivateコマンドでAnacondaから抜けた後、pyenv activateコマンドを実行する形となる。

$ conda deactivate
$ pyenv activate myenv
$ pyenv versions
  system
  anaconda3-2022.05
  anaconda3-2022.05/envs/myenv
* myenv (set by PYENV_VERSION environment variable)
$ conda env list
# conda environments:
#
base                  *  /Users/heroween/.anyenv/envs/pyenv/versions/anaconda3-2022.05
myenv                    /Users/heroween/.anyenv/envs/pyenv/versions/anaconda3-2022.05/envs/myenv

なお、pyenvでインストールしたAnacondaと、そのAnacondaを元にpyenv virtualenv(conda create)で作成した仮想環境とでは、デフォルトでインストールされるパッケージ群が異なるので注意が必要(仮想環境では作成時に出力されるメッセージの通り、最低限のパッケージしかインストールしない)。

SoftEther VPN Serverの管理者パスワードを変更する

AWSのEC2(OSはAmazon Linux 2)上で稼働しているSoftEther VPN Serverに設定していた管理者パスワードを忘れたという事案が生じ、管理者パスワードの再設定を行ったので、その手順を残しておく。

作業の流れとしては以下の通り。

  1. EC2に接続
  2. SoftEther VPN Serverのサービスを停止
  3. 既存の管理者パスワードを削除
  4. SoftEther VPN Serverのサービスを開始
  5. 新しい管理者パスワードを設定

作業手順

まずは、SofrEther VPN Serverの稼働しているEC2に、SSHなりSession Managerなりで接続する。

接続出来たら以下のコマンドでSoftEther VPN Serverのサービスを停止。

$ sudo su -
$ systemctl stop vpnserver

次に、既存の管理者パスワードを削除するのだが、パスワード自体はSoftEther VPN Serverの設定ファイルであるvpn_server.configにハッシュ化された形で定義されているので、それを直接編集して削除する必要がある。

件のサーバーでは/usr/local/ディレクトリにインストールされていたので、設定ファイルは/usr/local/vpnserver/vpn_server.configにあった。

中身を確認すると、以下の様にrootServerConfigurationHashedPasswordとして管理者パスワードが定義されている。

declare root
{
〜〜〜 中略 〜〜〜
        declare ServerConfiguration
        {
〜〜〜 中略 〜〜〜
                byte HashedPassword D2Ak3pm0cNSlPrtyCWYig1vDcFE=

このHashedPasswordを削除してやる。

$ cp -vip /usr/local/vpnserver/vpn_server.config /tmp ←バックアップ
$ vi /usr/local/vpnserver/vpn_server.config ←ServerConfiguration内のHashedPassword行を削除して保存

なお、ServerConfiguration以外にVirtualHUBにもHashedPassword(こちらは仮想ハブのパスワード)が定義されているので、HashedPasswordだけで検索して誤ってVirtualHUBの方を削除してしまわない様に注意。

vpn_server.configの編集を終えたら、SoftEther VPN Serverのサービスを再開。

$ systemctl start vpnserver

サービスの起動を確認したら、SofrEther VPN Serverの管理コマンドとなるvpncmdを使って、新しい管理者パスワードを設定する。

$ /usr/local/vpnserver/vpncmd

実行すると対話式のプロンプトに切り替わり、このコマンドで何をするのか選択肢が表示されるので、「1」(サーバー管理)を入力してエンターを押す。

vpncmd コマンド - SoftEther VPN コマンドライン管理ユーティリティ
SoftEther VPN コマンドライン管理ユーティリティ (vpncmd コマンド)
Version 4.34 Build 9745   (Japanese)
Compiled 2020/04/05 23:39:56 by buildsan at crosswin
Copyright (c) SoftEther VPN Project. All Rights Reserved.

vpncmd プログラムを使って以下のことができます。

1. VPN Server または VPN Bridge の管理
2. VPN Client の管理
3. VPN Tools コマンドの使用 (証明書作成や通信速度測定)

1 - 3 を選択: 1

すると、作業対象とするサーバーの接続先を問われるのだが、何も入力せずにエンターを押すとlocalhostが指定される。

接続先の VPN Server または VPN Bridge が動作しているコンピュータの IP アドレスまたはホスト名を指定してください。
'ホスト名:ポート番号' の形式で指定すると、ポート番号も指定できます。
(ポート番号を指定しない場合は 443 が使用されます。)
何も入力せずに Enter を押すと、localhost (このコンピュータ) のポート 443 に接続します。
接続先のホスト名または IP アドレス:

次に、仮想ハブ名の入力を求められるので、今回はサーバー管理モードで接続する為、ここでも何も入力せずにエンターを押す。

サーバーに仮想 HUB 管理モードで接続する場合は、仮想 HUB 名を入力してください。
サーバー管理モードで接続する場合は、何も入力せずに Enter を押してください。
接続先の仮想 HUB 名を入力:

管理者パスワードが設定されている状態だと、このタイミングでパスワードの入力を求められるのだが、vpn_server.configから正常に削除出来ていれば、このまま管理者モードでSoftEther VPN Serverに接続され、以下の様なプロンプトが表示される。

VPN Server "localhost" (ポート 443) に接続しました。

VPN Server 全体の管理権限があります。

VPN Server>

プロンプトが切り替わったら管理者パスワードの設定コマンドServerPasswordSetを実行し、新しいパスワードを2回入力すると設定が完了。

VPN Server>ServerPasswordSet
ServerPasswordSet コマンド - VPN Server の管理者パスワードの設定
パスワードを入力してください。キャンセルするには Ctrl+D キーを押してください。

パスワード: ************************
確認入力  : ************************


コマンドは正常に終了しました。

VPN Server>exit

一度、プロンプトを抜けてから、再度vpncmdを実行してlocalhostに接続しようとすると、先程とは異なり管理者パスワードの入力を求められる様になっている。

$ /usr/local/vpnserver/vpncmd
vpncmd コマンド - SoftEther VPN コマンドライン管理ユーティリティ
SoftEther VPN コマンドライン管理ユーティリティ (vpncmd コマンド)
Version 4.34 Build 9745   (Japanese)
Compiled 2020/04/05 23:39:56 by buildsan at crosswin
Copyright (c) SoftEther VPN Project. All Rights Reserved.

vpncmd プログラムを使って以下のことができます。

1. VPN Server または VPN Bridge の管理
2. VPN Client の管理
3. VPN Tools コマンドの使用 (証明書作成や通信速度測定)

1 - 3 を選択: 1

接続先の VPN Server または VPN Bridge が動作しているコンピュータの IP アドレスまたはホスト名を指定してください。
'ホスト名:ポート番号' の形式で指定すると、ポート番号も指定できます。
(ポート番号を指定しない場合は 443 が使用されます。)
何も入力せずに Enter を押すと、localhost (このコンピュータ) のポート 443 に接続します。
接続先のホスト名または IP アドレス:

サーバーに仮想 HUB 管理モードで接続する場合は、仮想 HUB 名を入力してください。
サーバー管理モードで接続する場合は、何も入力せずに Enter を押してください。
接続先の仮想 HUB 名を入力:
パスワード: ************************

VPN Server "localhost" (ポート 443) に接続しました。

VPN Server 全体の管理権限があります。

VPN Server>exit

ここで新しいパスワードを入力してlocalhostに接続される事を確認し、問題が無ければ管理者パスワードの変更作業は完了となる。

Redashコンテナの環境変数を更新する

OSSダッシュボードサービスであるRedashAWSで運用しているのだが(公式のEC2イメージを使用)、メールサーバーの設定を変更する必要があったので、その手順をまとめておく。

Redashはユーザー作成時の通知等でメールを使えるのだが、そのメールサーバーの設定はWebコンソールからでは行えず、Dockerコンテナの環境変数に定義してやる必要がある。

Dockerコンテナに読み込ませる環境変数ファイルは/opt/redash/envに設置されており、docker-composeによりevn_fileでコンテナに展開されている。

メールサーバー関連で最低限必要となる環境変数は以下の通り(詳細は公式ドキュメントを参照)。

  • REDASH_MAIL_SERVER
  • REDASH_MAIL_PORT
  • REDASH_MAIL_USE_TLS
  • REDASH_MAIL_USERNAME
  • REDASH_MAIL_PASSWORD
  • REDASH_MAIL_DEFAULT_SENDER

運用している環境ではAWS SES(東京リージョン)を使っていたので、定義している値はこんな感じ。

REDASH_MAIL_SERVER=email-smtp.ap-northeast-1.amazonaws.com
REDASH_MAIL_PORT=587
REDASH_MAIL_USE_TLS=true
REDASH_MAIL_USERNAME=AKIAXXXXXXXXXXXXXXXX
REDASH_MAIL_PASSWORD=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
REDASH_MAIL_DEFAULT_SENDER=no-reply@example.com

ここで定義していたSESのSMTP認証情報を変更した為、REDASH_MAIL_USERNAMEとREDASH_MAIL_PASSWORDを更新してRedashコンテナに反映しなければならなくなった。

docker-compose upで更新

Redashコンテナの環境変数を更新するのは実に簡単で、/opt/redash/envの内容を変更した後、docker-compose up -dでコンテナを再作成してやるだけ。

$ sudo su -
$ cd /opt/redash/
$ vi env ←定義された値を編集
$ docker-compose up -d

docker-compose stop/startでコンテナを再起動するだけでは、稼働中のコンテナがenv_fileに定義された環境変数を再展開しない為、一旦、コンテナ自体を作り直してやる必要がある。

なお、Redashのデータを保管するpostgresコンテナのVolumeは、ホスト側にマウントされて永続化しているので、コンテナを再作成したとしてもデータが失われる事は無い。

docker-compose stop/startで更新

一応、コンテナを作り直す事はせずに、コンテナファイル自体を書き換えてしまい、dockerデーモンも再起動するのであれば、docker-compose stop/startでコンテナの環境変数を更新する事も出来る。

Dockerコンテナは起動すると、コンテナ毎に/var/lib/docker/containers/コンテナフルIDといったディレクトリが作成され、その配下に各種の関連ファイルを保存している。

例えば、コンテナID(省略形)「584d782142d7」が起動した場合であると、

/var/lib/docker/containers/584d782142d75d3d67738007f054cccbc846ac22b2e3ad370013754c0f491fda

といったディレクトリが出来て、配下にはコンテナの実体に関わる様々なファイル群が配置される。

$ cd /var/lib/docker/containers/584d782142d75d3d67738007f054cccbc846ac22b2e3ad370013754c0f491fda/
$ ls -la
-rw-r----- 1 root root 339367  8月 31 21:12 584d782142d75d3d67738007f054cccbc846ac22b2e3ad370013754c0f491fda-json.log
drwx------ 2 root root   4096  8月 31 19:30 checkpoints
-rw------- 1 root root   4162  8月 31 19:30 config.v2.json
-rw-r--r-- 1 root root   1487  8月 31 19:30 hostconfig.json
-rw-r--r-- 1 root root     13  8月 31 19:30 hostname
-rw-r--r-- 1 root root    174  8月 31 19:30 hosts
drwx------ 3 root root   4096  8月 31 19:30 mounts
-rw-r--r-- 1 root root     77  8月 31 19:30 resolv.conf
-rw-r--r-- 1 root root     71  8月 31 19:30 resolv.conf.hash

この内の、config.v2.jsonというファイルがコンテナの状態を記録しており、

$ cat config.v2.json
{"StreamConfig":{},"State":{"Running":true, 〜〜〜 中略 〜〜〜 ,"SeccompProfile":"","NoNewPrivileges":false}

JSON中に以下の様なリスト形式でコンテナに展開されている環境変数を持っている。

"Env":["REDASH_MAIL_SERVER=email-smtp.ap-northeast-1.amazonaws.com","REDASH_MAIL_PORT=587", 以下略]

なので、このファイル自体を書き換えてしまえば、コンテナを作成し直さずに、コンテナの環境変数を更新する事が出来る。

手順としては、起動中のコンテナ、Dockerデーモンという順番で停止してから、config.v2.jsonのEnvの値を書き換えて、Dockerデーモン、コンテナと起動しなおす形になる。

$ sudo su -
$ cd /opt/redash/
$ docker-compose stop
$ systemctl stop docker
$ cd /var/lib/docker/containers/コンテナフルID/
$ cp -vip config.v2.json ~/tmp ←バックアップ
$ vi config.v2.json ←Envの値を書き換える
$ cd -
$ systemctl start docker
$ docker-compose start

config.v2.json環境変数だけを保持している訳では無いので、環境変数以外の設定を変えたいといった場合にも色々と応用は可能である。

しかし、Redashコンテナの環境変数を更新するだけなのであれば、面倒なのでわざわざこの方法をとる必要性はあまり無いかと思う。

Dockerコンテナのレイヤーが増えないので、ストレージ容量的には優しいというメリットが微妙にあるかもしれない。

ちなみに、Redashを構成するコンテナ群は以下の様になっているのだが、

  • nginx
  • server
  • scheduler
  • scheduled_worker
  • adhoc_worker
  • redis
  • postgres

ユーザー作成時にメール送信するのはschedulerなので、メールの利用がそこだけなのであれば、最低限、schedulerのconfig.v2.jsonだけを更新すれば事は足りる。

API Gatewayの実行エラー「Execution failed due to an vpc link error」について

API GatewayVPCリンクエラー

API GatewayVPCリンクを使ったREST API(バックエンドはECSサービス)において、単発の500エラーを検出したのだが、その際、実行ログに以下の様なエラーメッセージが出力された。

(xx389x71-xx67-442x-9073-441x13535xx1) Execution failed due to an vpc link error

実行ログの流れを確認すると、エンドポイントリクエスト後に、このエラーが生じてAPI実行が異常終了しており、メッセージのままの意味であれば、VPCリンクに何かしらの異常が生じたものと思われる。

HTTP APIの場合、VPCリンクは60日間トラフィックが生じないと、ステータスがAVAILABLEからINACTIVEへと変わり、VPC内の専用ENIが削除されてしまい、API通信が一時的に不能状態に陥るといった仕様がある(再びAPIリクエストされるとENIが復活するものの、プロビジョニング完了までの数分は通信不能が続く)。

しかし、問題の起きたAPI GatewayREST APIで、VPCリンクにはVPCエンドポイントサービスが使われており、こちらはHTTP APIとは異なり、長期間のトラフィックが生じないと自動削除されるといった仕様にはなっていない(そして、このAPIは常時、高トラフィックでもあった)。

VPCリンクに設定しているVPCエンドポイントサービスやNLBのメトリクスを確認したのだが、特段の異常が見られなかった為、明確な原因を把握しておきたく、このメッセージを調べてみたものの、公式の開発者ガイドや検索結果に引っかかるものは無かった。

エラーの原因

自身で調べてみても、結局分からなかったので、AWSサポートに問い合わせてみた。

回答としては、やはり、API GatewayからNLB間における一時的な通信障害に起因するもの(明確にどこで発生したもの、というところまでは判明しなかったけど)、という事であった。

通信において経路上のどこでもエラーが発生する可能性はあり、完全に無くす事は出来ない為、処理上で問題となるのであれば、対応としてAWSのドキュメントにもある様に、クライアントサイドでリトライを実装するのが望ましい。

今回のケースでは頻発していた訳でもなく、単発のエラーであったのと、そもそも処理としてリトライもされており、特に大きな問題でもなかったので、この回答をもって調査を終了とした。

API GatewayVPCリンクを使ったREST APIを利用する上では、VPCエンドポイントサービスやNLBで確認出来るステータスやメトリクスに異常が無くとも、こういったエラーは生じ得る、という事を念頭に置いておく必要がある。

aws-sqsdのSignature(署名リクエスト)エラーについて

aws-sqsdで発生したエラー

Elastic BeanstalkのWorkerインスタンスがSQSのメッセージを捌かなくなり、5XXを頻発するという障害の調査を依頼され、aws-sqsdを確認したところ、ログに以下の様なエラーが出力されていた(タイムスタンプは架空)。

2022-07-24T00:01:02Z healthcheck-err: failed to pull messages from SQS queue with error: Signature not yet current: 20220724T000101Z is still later than 20220723T230411Z (20220723T224911Z + 15 min.)
2022-07-24T00:01:02Z pollers: daemon exception during polling message from sqs: Signature not yet current: 20220724T000102Z is still later than 20220723T230411Z (20220723T224911Z + 15 min.)
2022-07-24T00:01:02Z metrics: Aws::CloudWatch::Errors::SignatureDoesNotMatch: Signature not yet current: 20220724T000102Z is still later than 20220723T225411Z (20220723T224911Z + 5 min.)
2022-07-24T00:01:02Z pollers: daemon exception during polling message from sqs: Signature not yet current: 20220724T000102Z is still later than 20220723T230412Z (20220723T224912Z + 15 min.)

メッセージを読むと時刻のズレに原因があるものと推察出来る。

実際にその通りで、これはAWSAPIを実行したマシンの時刻が現在時からズレていると生じる、AWS APIの署名リクエストのエラーの一種。

AWS APICLISDK等でのリクエスト時に、APIの実行時刻をx-amz-dateリクエストヘッダー、乃至はクエリパラメーターとして付与するのだが、その時刻がAPIサーバー側とズレているとこのエラーが返ってくる。

詳細な仕様は下記リンク先の公式ドキュメントを参照。

署名リクエストのエラーは要素に応じて何種類かあるのだが、CLISDKを使ってAWSのリソースを操作する場合、ユーザーが意識せずとも暗黙的に正規のリクエストが送られる為、署名リクエストのエラーの中でも、ユーザー側の環境起因で発生する事になるこのエラーに遭遇する確率は高い気がする。

なお、この様なエラーは当然だがaws-sqsdに限らず、他のAWSサービスにおいても発生するもので、S3とかだと以下の様なエラーメッセージをちょくちょく目にする事がある。

The difference between the request time and the current time is too large.

職場でAWSの技術サポート的な事をやっていると、S3の操作が出来ないといった相談を受け、エラーメッセージを送ってもらったらこれで、作業マシンの時刻がズレていました、というのは割とあるあるだったりする。

エラーの原因

上述したaws-sqsdのエラーの原因自体は既に書いた通りなのだが、今回、このエントリを書く切っ掛けとなった障害は、Amazon Linux 2で稼働しているElastic BeanstalkのWorkerインスタンスで発生していた。

Amazon Linux 2の場合、タイムサーバーはchronyが稼働しており、標準で169.254.169.123のリンクローカルIPアドレスでアクセス出来るAmazon Time Sync Serviceを利用して時刻の同期が行われている。

にも関わらず、件のWorkerインスタンスで時刻を確認したところ、数時間単位で未来時刻にズレていたのである。

どういう事だろうかと、まず、journalctlでchronydを見てみたら、以下の様なメッセージが表示された(タイムスタンプは架空)。

Jul 24 07:45:53 ip-xxx-xxx-xxx-xxx.ap-northeast-1.compute.internal chronyd[1683]: Forward time jump detected!
Jul 24 07:46:27 ip-xxx-xxx-xxx-xxx.ap-northeast-1.compute.internal chronyd[1683]: Can't synchronise: no selectable sources
Jul 24 07:49:16 ip-xxx-xxx-xxx-xxx.ap-northeast-1.compute.internal chronyd[1683]: Selected source 169.254.169.123
Jul 24 09:59:52 ip-xxx-xxx-xxx-xxx.ap-northeast-1.compute.internal systemd[1]: chronyd.service: main process exited, code=exited, status=1/FAILURE
Jul 24 09:59:52 ip-xxx-xxx-xxx-xxx.ap-northeast-1.compute.internal systemd[1]: Unit chronyd.service entered failed state.
Jul 24 09:59:52 ip-xxx-xxx-xxx-xxx.ap-northeast-1.compute.internal systemd[1]: chronyd.service failed.

唐突に時間が進んでしまい、AWS Time Sync Serviceとの同期が切れた後、プロセスが異常終了してしまった模様で、そのまま再起動する事も無く、chronydがお亡くなりになっていた。

この前に何があったのか、全体のログを確認してみたところ、直前にOOM Killerが発生しており、aws-sqsdのプロセスが殺されて、chronyd以外のサービスにも色々と異常をきたしていた事が判明。

sysstatでリソースの状況を確認していくと、インスタンスが起動した段階からメモリの使用率は90%程度になっており、aws-sqsdがメッセージを処理し始めてから1時間程度でメモリがパンク寸前に。

また、journalctl -u chronydでユニット指定で絞り込んだ時には表示されなかったが、systemdがchronydサービスを異常終了した後に、以下のエラーメッセージが出力されていた事も分かった。

Jul 24 09:59:52 ip-xxx-xxx-xxx-xxx chronyd[1683]: Fatal error : Possible infinite loop in scheduling

このエラーメッセージを検索してみたところ、chronyユーザーのメーリングリストアーカイブが引っ掛かり、同じ様にメモリの高負荷状況下でchronydがクラッシュして再起動に失敗するという事例が報告されていた。

スレッドを追っていくと、chronyのメンテナーによる返信として、メモリの枯渇でページロードを待機し続けてこのエラーが生じていると推測されており、その後、メモリの高負荷時にこのエラーが出る旨が書かれている。

という訳で、根本原因としてメモリの容量不足が考えられる事は間違い無さそうである。

時刻の同期が狂った状況について、chronyの各種ログを見ればより詳細が分かるのかもしれないが、chronyのログは標準だとsyslogに吐かれるもの以外は出力されない設定になっている。

chronyの設定ファイルである/etc/chrony.conflogコマンドは以下の通りコメントアウトされており、/var/log/chronyディレクトリには何も吐かれない。

# Specify directory for log files.
logdir /var/log/chrony

# Select which information is logged.
#log measurements statistics tracking

Amazon Linux 2でもこの様な設定なので、残念ながらこれ以上の事は分からなかった。

対応と課題

そもそも、chronyの問題は置いておいたとしても、少なくともメモリの容量が不足しているという問題点は明白だったので、まずは、Workerインスタンスのスケールアップで当座を凌いでもらい、Workerアプリケーションのプロセスやメモリ使用量の監視を後日の課題として、一先ずの調査を終えた。