Macで自分のHostNameの解決が遅い

  • このエントリーをはてなブックマークに追加

Macで開発していたらHostNameの参照が遅いという事がありました。
例えばJavaのLoggerのLogbackではHostnameを自動で取得します。

HOSTNAME変数とCONTEXT_NAME変数はよく使われるので、コンテキストスコープを持つ変数として自動的に定義されます。

自分のHostNameを正しく解決できないと10秒ほどBlockされるので、テストケースが100個あるとその都度待たされてしまうわけです。

実際に詰まっているのを確認したら、IPv6のアドレスを求めるところで詰まってました。

"pool-1-thread-1-ScalaTest-running-FormDefinitionsTest" #12 prio=5 os_prio=31 tid=0x00007fc13882d800 nid=0xa903 runnable
[0x00007000026bb000]
java.lang.Thread.State: RUNNABLE
at java.net.Inet6AddressImpl.lookupAllHostAddr(Native Method)
at java.net.InetAddress$2.lookupAllHostAddr(InetAddress.java:928)
at java.net.InetAddress.getAddressesFromNameService(InetAddress.java:1323)
at java.net.InetAddress.getLocalHost(InetAddress.java:1500)
- locked <0x0000000740866718> (a java.lang.Object)
at ch.qos.logback.core.util.ContextUtil.getLocalHostName(ContextUtil.java:37)
at ch.qos.logback.core.util.ContextUtil.addHostNameAsProperty(ContextUtil.java:67)
at ch.qos.logback.classic.joran.action.ConfigurationAction.begin(ConfigurationAction.java:59)
at ch.qos.logback.core.joran.spi.Interpreter.callBeginAction(Interpreter.java:269)
at ch.qos.logback.core.joran.spi.Interpreter.startElement(Interpreter.java:145)
at ch.qos.logback.core.joran.spi.Interpreter.startElement(Interpreter.java:128)
at ch.qos.logback.core.joran.spi.EventPlayer.play(EventPlayer.java:50)
at ch.qos.logback.core.joran.GenericConfigurator.doConfigure(GenericConfigurator.java:148)
- locked <0x0000000740f7f370> (a ch.qos.logback.core.spi.LogbackLock)
at ch.qos.logback.core.joran.GenericConfigurator.doConfigure(GenericConfigurator.java:135)
at ch.qos.logback.core.joran.GenericConfigurator.doConfigure(GenericConfigurator.java:100)
at ch.qos.logback.core.joran.GenericConfigurator.doConfigure(GenericConfigurator.java:50)
at play.api.Logger$$anonfun$configure$3.apply(Logger.scala:256)
at play.api.Logger$$anonfun$configure$3.apply(Logger.scala:256)
at scala.Option.foreach(Option.scala:257)
....

jshellで確認できます。

jshell> InetAddress localhost = InetAddress.getLocalHost();
localhost ==> SN0298.local/192.168.11.4

原因と解決策

次のコマンドを実行した時に、scutil --get HostNameの結果が空なのが原因です。

scutil --get HostName       #
scutil --get LocalHostName # SN0298
scutil --get ComputerName # SN0298

次のコマンドで設定する事で対応できます。

sudo scutil --set HostName $(scutil --get LocalHostName)

jshell> InetAddress localhost = InetAddress.getLocalHost();
localhost ==> SN0298/192.168.11.4

.localが無くなりますが、まぁ気にしない方向で。

検索すると/etc/hostsSN0298 127.0.0.1のようにする解決策を見かけます。この方法でも良いのですが、それだと上記のコードでDHCPのアドレスが取得できなくなります。

Maven RepositoryをGCSに構築する

  • このエントリーをはてなブックマークに追加

Maven Repositoryを作るとき、一般的にはNexusやJFrogを使いますが、APIサーバが必要なわけでもないのでGCPのCloud Storageに構築します。

これを使います。こちらのサイトを参考にします。

まずGCSにBucketを作る

適当な名前で作ります。Private Repositoryとして作成するのでPublicアクセスはOFFのままにしておきます。

pom.xmlにplugin(extension)の設定を追記

<build>
<extensions>
<extension>
<groupId>com.gkatzioura.maven.cloud</groupId>
<artifactId>google-storage-wagon</artifactId>
<version>1.7</version>
</extension>
</extensions>
</build>

バージョンは1.7を指定します。今時点では2.0がリリースされていますが、1.8からバグっていて、GCSにはアップロードできません。おそらくこのコミットで動かなくなったのだと思います。

成果物のアップロード先を指定します。

<distributionManagement>
<snapshotRepository>
<id>hoge-fuga-snapshot</id>
<url>gs://hogefuga-bucket/snapshot</url>
</snapshotRepository>
<repository>
<id>hoge-fuga-release</id>
<url>gs://hogefuga-bucket/release</url>
</repository>
</distributionManagement>

GCSの認証

通常はsetting.xmlに記述しますが、このPluginはその設定情報を使いません。

gcloud auth login –brief で認証するか、環境変数のGOOGLE_APPLICATION_CREDENTIALSを設定する必要があります。

WSLでSSHの鍵を転送するとBSoDで死ぬ件について

  • このエントリーをはてなブックマークに追加

関連Issue: https://github.com/Microsoft/WSL/issues/3916

KB4489868 を削除して再起動したら直りました。いや、回避しただけで直ってはいないわけですが。

追記 2019/04/08

Windows10のパッチバージョン1809をインストールするとBSoDが発生しなくなりました。

HttpClientはデフォルトでAccept Headerを付けない

  • このエントリーをはてなブックマークに追加

ApacheのHttpClinetはデフォルトでAcceptヘッダを付与しません。
例えば、簡単なサンプルを次に示します。

SSLContextConnectionManager,RequestConfigを設定していませんが、そこは本質的ではないので省略しています。

HttpClientBuilder clientBuilder = HttpClientBuilder.create();
try (CloseableHttpClient client = clientBuilder.build()) {
HttpGet getMethod = new HttpGet();
getMethod.setURI(URI.create("https://httpstat.us/200"));
try (CloseableHttpResponse resp = client.execute(getMethod)) {
String body = EntityUtils.toString(resp.getEntity(), StandardCharsets.UTF_8);
logger.info("header:{}", resp.getStatusLine());
logger.info("body:{}", body);
}
}

HttpClientのDebugLogをみたいので、LogレベルをDEBUGに設定していまいます。あとコピペしやすいようにPatternをちょっとだけ修正。

Logger rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
rootLogger.setLevel(Level.DEBUG);

PatternLayoutEncoder ple = new PatternLayoutEncoder();
ple.setContext(rootLogger.getLoggerContext());
ple.setPattern("%-50class{1} %msg%n");
ple.start();

ConsoleAppender consoleAppender = (ConsoleAppender) rootLogger.getAppender("console");
consoleAppender.setEncoder(ple);

実行結果です。必要なところだけを抜き出しています。

RunningLog
o.a.h.i.e.MainClientExec                           Executing request GET /200 HTTP/1.1
o.a.h.i.e.MainClientExec Target auth state: UNCHALLENGED
o.a.h.i.e.MainClientExec Proxy auth state: UNCHALLENGED

o.a.h.i.c.LoggingManagedHttpClientConnection http-outgoing-0 >> GET /200 HTTP/1.1
o.a.h.i.c.LoggingManagedHttpClientConnection http-outgoing-0 >> Host: httpstat.us
o.a.h.i.c.LoggingManagedHttpClientConnection http-outgoing-0 >> Connection: Keep-Alive
o.a.h.i.c.LoggingManagedHttpClientConnection http-outgoing-0 >> User-Agent: Apache-HttpClient/4.5.4 (Java/11.0.2)
o.a.h.i.c.LoggingManagedHttpClientConnection http-outgoing-0 >> Accept-Encoding: gzip,deflate
o.a.h.i.c.Wire http-outgoing-0 >> "GET /200 HTTP/1.1[\r][\n]"
o.a.h.i.c.Wire http-outgoing-0 >> "Host: httpstat.us[\r][\n]"
o.a.h.i.c.Wire http-outgoing-0 >> "Connection: Keep-Alive[\r][\n]"
o.a.h.i.c.Wire http-outgoing-0 >> "User-Agent: Apache-HttpClient/4.5.4 (Java/11.0.2)[\r][\n]"
o.a.h.i.c.Wire http-outgoing-0 >> "Accept-Encoding: gzip,deflate[\r][\n]"
o.a.h.i.c.Wire http-outgoing-0 >> "[\r][\n]"

o.a.h.i.c.Wire http-outgoing-0 << "HTTP/1.1 200 OK[\r][\n]"
o.a.h.i.c.Wire http-outgoing-0 << "Cache-Control: private[\r][\n]"
o.a.h.i.c.Wire http-outgoing-0 << "Server: Microsoft-IIS/10.0[\r][\n]"
o.a.h.i.c.Wire http-outgoing-0 << "X-AspNetMvc-Version: 5.1[\r][\n]"
o.a.h.i.c.Wire http-outgoing-0 << "Access-Control-Allow-Origin: *[\r][\n]"
o.a.h.i.c.Wire http-outgoing-0 << "X-AspNet-Version: 4.0.30319[\r][\n]"
o.a.h.i.c.Wire http-outgoing-0 << "X-Powered-By: ASP.NET[\r][\n]"
o.a.h.i.c.Wire http-outgoing-0 << "Set-Cookie: ARRAffinity=beec3692495883b8df6195e900c12f49514e054d865a22ad2951c84f51dbaf93;Path=/;HttpOnly;Domain=httpstat.us[\r][\n]"
o.a.h.i.c.Wire http-outgoing-0 << "Date: Sun, 17 Mar 2019 08:21:05 GMT[\r][\n]"
o.a.h.i.c.Wire http-outgoing-0 << "Content-Length: 0[\r][\n]"
o.a.h.i.c.Wire http-outgoing-0 << "[\r][\n]"

o.a.h.i.c.LoggingManagedHttpClientConnection http-outgoing-0 << HTTP/1.1 200 OK
o.a.h.i.c.LoggingManagedHttpClientConnection http-outgoing-0 << Cache-Control: private
o.a.h.i.c.LoggingManagedHttpClientConnection http-outgoing-0 << Server: Microsoft-IIS/10.0
o.a.h.i.c.LoggingManagedHttpClientConnection http-outgoing-0 << X-AspNetMvc-Version: 5.1
o.a.h.i.c.LoggingManagedHttpClientConnection http-outgoing-0 << Access-Control-Allow-Origin: *
o.a.h.i.c.LoggingManagedHttpClientConnection http-outgoing-0 << X-AspNet-Version: 4.0.30319
o.a.h.i.c.LoggingManagedHttpClientConnection http-outgoing-0 << X-Powered-By: ASP.NET
o.a.h.i.c.LoggingManagedHttpClientConnection http-outgoing-0 << Set-Cookie: ARRAffinity=beec3692495883b8df6195e900c12f49514e054d865a22ad2951c84f51dbaf93;Path=/;HttpOnly;Domain=httpstat.us
o.a.h.i.c.LoggingManagedHttpClientConnection http-outgoing-0 << Date: Sun, 17 Mar 2019 08:21:05 GMT
o.a.h.i.c.LoggingManagedHttpClientConnection http-outgoing-0 << Content-Length: 0
o.a.h.i.e.MainClientExec Connection can be kept alive indefinitely

o.a.HttpClientSample header:HTTP/1.1 200 OK
o.a.HttpClientSample body:

これを見るとAcceptヘッダを送信していません。

何が問題か?

ブラウザやcurlはデフォルトでAcceptを送信しており、そこには*/*が含まれています。
参照: https://developer.mozilla.org/ja/docs/Web/HTTP/Content_negotiation/List_of_default_Accept_values

サンプルに利用したhttpstatでAcceptヘッダがある場合と無い場合とで確認してみましょう。

include_accept_header
$ curl http://httpstat.us/200 -v
* Trying 23.99.0.12...
* TCP_NODELAY set
* Connected to httpstat.us (23.99.0.12) port 80 (#0)
> GET /200 HTTP/1.1
> Host: httpstat.us
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Cache-Control: private
< Content-Length: 6
< Content-Type: text/plain; charset=utf-8
< Server: Microsoft-IIS/10.0
< X-AspNetMvc-Version: 5.1
< Access-Control-Allow-Origin: *
< X-AspNet-Version: 4.0.30319
< X-Powered-By: ASP.NET
< Set-Cookie: ARRAffinity=beec3692495883b8df6195e900c12f49514e054d865a22ad2951c84f51dbaf93;Path=/;HttpOnly;Domain=httpstat.us
< Date: Sun, 17 Mar 2019 08:33:52 GMT
<
* Connection #0 to host httpstat.us left intact
200 OK
exclude_accept_header
$ curl -H "Accept:"  http://httpstat.us/200 -v
* Trying 23.99.0.12...
* TCP_NODELAY set
* Connected to httpstat.us (23.99.0.12) port 80 (#0)
> GET /200 HTTP/1.1
> Host: httpstat.us
> User-Agent: curl/7.58.0
>
< HTTP/1.1 200 OK
< Cache-Control: private
< Server: Microsoft-IIS/10.0
< X-AspNetMvc-Version: 5.1
< Access-Control-Allow-Origin: *
< X-AspNet-Version: 4.0.30319
< X-Powered-By: ASP.NET
< Set-Cookie: ARRAffinity=beec3692495883b8df6195e900c12f49514e054d865a22ad2951c84f51dbaf93;Path=/;HttpOnly;Domain=httpstat.us
< Date: Sun, 17 Mar 2019 08:36:54 GMT
< Content-Length: 0
<
* Connection #0 to host httpstat.us left intact

Accpetヘッダを送信しないと、Content-Lengthヘッダが0になってBODYが空のレスポンスが返ってきました。

このサイトに限らず、例えばAcceptヘッダでapplication/jsonだとJSON形式で返すようなサイトでは、Frameworkによってはこういう挙動になるかもしれません。

どう書けばいいのか

いくつかやり方がありますがDefaultHeaderを設定するのが楽だと思います。

List<BasicHeader> defaultHeaders = Arrays.asList(new BasicHeader("Accept", "*/*"));
HttpClientBuilder clientBuilder = HttpClientBuilder.create()
.setDefaultHeaders(defaultHeaders);

もしくはGetMethodに設定します。

getMethod.setHeader("Accept", "*/*");

GCPのプロジェクトを組織なしで作成する方法

  • このエントリーをはてなブックマークに追加

GMAILアカウントでGCPを利用している場合は組織という概念がない(?)ので、常に「組織なし」の状態で作られます。
GSuiteで管理しているドメイン(会社のドメイン)でプロジェクトを作成すると、設定にもよりますがそのGSuiteの組織の下にプロジェクトが作られます。

組織の下にプロジェクトを作成すると、組織そのものにIAMを追加しておけば配下の全てのプロジェクトに引き継がれるので毎回オーナーを設定すると言った煩わしい作業が無くなるので楽なのですが、組織の下に置くとプロジェクトを組織から移動させる事が大変になります。

例えば、会社はサポートしているんだけどOSSとしてやっているプロジェクトなんかは組織に紐付けない方が良いものがあります。もしくは将来別組織に移動する予定があるプロジェクトとか。

どう作るか?

  1. 個人のアカウントでプロジェクトを作成する
  2. 課金アカウントを紐付けている場合は課金アカウントを外す
  3. そのプロジェクトに組織ドメインのユーザーをオーナーとして追加する
  4. そのプロジェクトの課金アカウントを組織ドメインの課金アカウントに紐付ける
  5. 個人のユーザーをそのプロジェクトから外す

普通はこんな事やりませんが、ちょっと諸事情で必要になったので調べたのでした。

ハマったこと

会社のアカウントでやる前に個人のGSuiteアカウントで予行練習しようとしたら、次の事にハマりました。マニュアル読めばきっと書いてある内容ではあると思う。

  • 最初GSuiteで管理しているGCPのアカウントを作ったら、プロジェクトが作れなかった。組織のIAM設定でユーザーを追加する必要があった
  • 組織の下にはフォルダを作ることができる。しかし組織の管理者でもオーナーでもこのフォルダを作ることが出来なかった。フォルダ作成者といった権限が必要だった。なんでもかんでもオーナーだからと言って設定出来るわけではないらしい。組織の管理者ではだめだった。

rsyslogのTCP転送のConnectionを定期的にRefreshする

  • このエントリーをはてなブックマークに追加

rsyslogでリモートサーバにログを転送する時、転送先を冗長化するためにLBを使うことがあると思います。AWSだとNLBを使うことになります。

rsyslogはログを転送するたびにsocketを貼るわけではなく、貼りっぱなしの状態になります。そのため、NLB配下に複数サーバがあっても均等に割り当てられるとは限らないため、負荷が均等に分散されません。解決策として、Client側で定期的にSocketを張り替える必要があります。

設定方法

こちらのマニュアルにRebindIntervalの項目があるため、これを利用します。TCPなので$ActionSendTCPRebindIntervalを使います。

Permits to specify an interval at which the current connection is broken and re-established. This setting is primarily an aid to load balancers. After the configured number of messages has been transmitted, the current connection is terminated and a new one started. Note that this setting applies to both TCP and UDP traffic. For UDP, the new connection'' uses a different source port (ports are cycled and not reused too frequently). This usually is perceived as a new connection’’ by load balancers, which in turn forward messages to another physical target system.

この設定項目は、公式サイトより、バージョン4.5.1からサポートされています。しかしながら、同じく公式サイトにはバージョン6.3.6からと書いてあります。

ソースコードを見ると4.5.1からサポートされていますし、AmazonLinuxでは5.8.10ですが当然このバージョンでもサポートされています。

指定する単位はメッセージ数です。秒ではないです。
例えば以下のように設定すると、10000メッセージを送ったタイミングで再接続します。数値は実際のログの流量によって調整すると良いです。

rsyslog.conf
$ActionSendTCPRebindInterval 10000 # reconnect per message count

ssh forwardで転送している鍵をdocker containerの中で使う方法

  • このエントリーをはてなブックマークに追加

docker containerの中でAgent転送した鍵を使いたいことがあります。
次のような場合です。
bastionにagent forwardで鍵を転送して、その鍵をdockerの中に持ち込みたい。

mac -> bastion
docker run fabric
[in container]
fabricでsshの設定が必要

AgentForwardしていると環境変数$SSH_AUTH_SOCKが設定され、そこにsocketファイルのPATHが入っています。これをdockerの中に持ち込めれば良いので、次のようにします。

docker run -it --rm \
-v $SSH_AUTH_SOCK:/ssh-agent \
-e SSH_AUTH_SOCK=/ssh-agent \
image command

もしbastionに存在する鍵や、SSHの設定も一緒にdocker containerの中で使いたい場合は、.sshもmountします。

-v ${HOME}/.ssh:/root/.ssh -v $SSH_AUTH_SOCK:/ssh-agent -e SSH_AUTH_SOCK=/ssh-agent

なおmountする時に、mount元が存在しない場合はrootでディレクトリが作られてしまうので注意が必要です。-v /home/user/.m2:/root/.m2とした場合、/home/user/.m2というディレクトリが存在しないとrootユーザーの.m2ディレクトリが作成されてしまいます。

参考

datadogのauto mutingとlimit metrics collectionについて

  • このエントリーをはてなブックマークに追加

9月11日(火)の早朝から、DatadogのAutomutingが効かなくなりました。その原因を探ったのでその記録です。

AutoMutingとは?

ASGを使って、ScaleOut/ScaleInを頻繁にするようなシステムの場合、ScaleInで縮退する時(Instanceの削除)にはそのInstanceは監視対象外にしたいです。でないと、Instanceが削除されるたびにMetricsが来ないNODATAやdatadog.agent.upを見ている場合にアラートが発生します。

それを解決するためにDatadogではAutomutingをサポートしており、CloudWatchの情報を使ってInstanceが死んだら自動でInstanceのMuteをするようにしています。
これはDatadogのAWSとの連携設定の中にあります。

何が原因なのか?

ホストベースの簡単なMonitorを作成すると、次のようなクエリの監視ができあがります。

"datadog.agent.up".over("*").by("host").last(2).count_by_status()

これは通常、InstanceがTerminatedされた時にはDatadog側でそのホストは自動的にMuteされるのですが、この機能が動かずアラートが鳴りまくるという事象が発生しました。

原因について

結論から言えば、Datadog側の挙動が変わりました。サポートに連絡したところ、いくつかのIssueがあって解決中とのことなので、また元の挙動に戻るかもしれません。
ひとまず、現時点での挙動を記録に残しておきます。

原因判明について

こちらが解決のヒントになりました。
ありがたや、ありがたや。

このスレッドに、様々なスクリーンショットがありますのでご参考まで。

現時点の挙動について

Datadogは課金対象のInstanceを、Agentが動いている もしくは Datadogが認識したEC2インスタンスとしています。Agentを動かしていなくても、DatadogがEC2 Instanceを認識すればCloudWatch経由でMetricsを取ってきます。これも課金対象になります。
そのため、開発用にささっと立てたInstanceにDatadogのAgentを入れていなくても課金対象になってしまいます。

それを回避するため、limit metrics collectionの設定をします(下図参照)。例えば、ここにdatadog:enabledと入れるとAWSのタグにdatadog:enabledと設定されているInstanceが対象になります。(※当然ですがタグの設定がなくてもAgentが動いていれば課金対象です)

これらがどのように変わったのか、下の図にまとめました。
端的に言えば、limit metrics collectionの設定がある場合に、EC2にそのTagを設定していなくてもAgentの起動をしていればAutomutingの対象でしたが、該当日から対象外になりました。

Before

Agent起動 Tag有り 課金対象 Automutingが動作するか
F F F F
F T T T
T F T T
T T T T

Aftre

Agent起動 Tag有り 課金対象 Automutingが動作するか
F F F F
F T T T
T F T F
T T T T

サポートに連絡したところ、最終的には次の回答をもらいました。

I found out that we do have some issues with the EC2 automuting feature and the autoscaling groups. Our engineers are currently working on it.
We’re sorry for the inconvenience and will keep you updated.

しばらく様子を見ます。

なお、タグの設定を一回外すということをすると、このように課金対象ホストが増えてしまうので注意です。(これは実験のために意図して外したので真似はしないように。。)

MySQLのCLIで日本語が入力できない

  • このエントリーをはてなブックマークに追加

文字コードの設定関係なしに、MySQLのCLIに日本語を貼り付けても入力できないことがあります。AmazonLinuxのMySQL56がそうでした。

参考: MySQLクライアントに日本語が入力できない理由

理由は上記のブログにも書いてあるとおり、ReadlineではなくLibEditをLinkしているからなのですが、実はLibEditは2016年にマルチバイトに対応しています。

http://thrysoee.dk/editline/

2016-06-18 Jess Thrysoee

  • version-info: 0:54:0

  • all: sync with upstream source

  • deprecate option –enable-widec.
    Upstream now always build with unicode (wide-char/UTF-8) support.

ではなぜ入力できないのか、それはAmazonLinuxに入っているlibeditが古いからです。2008年のものです。とても古いです😱

$ sudo yum list | grep libedit

libedit.x86_64 2.11-4.20080712cvs.1.6.amzn1 installed
libedit.i686 2.11-4.20080712cvs.1.6.amzn1 amzn-main
libedit-devel.x86_64 2.11-4.20080712cvs.1.6.amzn1 amzn-main
$ mysql --version
mysql Ver 14.14 Distrib 5.6.41, for Linux (x86_64) using EditLine wrapper

対応はいくつかあります。

  • MySQL55を使う
  • MySQL56を使いつつ、LibEditは新しいものを使う

MySQL55を使う

MySQL5.6.12からReadlineではなくなっています。そのため、MySQL55のクライアントを使えば日本語は入力できます。

$ sudo yum install mysql55
$ mysql --version
mysql Ver 14.14 Distrib 5.5.61, for Linux (x86_64) using readline 5.1

新しいLibEditを使う

そうは言ってもMySQL5.6や5.7のクライアントを使いたい。
幸いにもstatic linkではないので、新しいLibEditをインストールしてそちらを参照するようにしてみます。

$ sudo yum install mysql56
$ ldd /usr/bin/mysql | grep libedit
libedit.so.0 => /usr/lib64/libedit.so.0 (0x00007fd0a7a03000)

ビルドしてインストール

wget http://thrysoee.dk/editline/libedit-20180525-3.1.tar.gz
tar zxvf libedit-20180525-3.1.tar.gz
cd libedit-20180525-3.1

sudo yum install gcc ncurses-devel
./configure --prefix=/usr/local --libdir=/usr/local/lib64
make
sudo make install

何も指定しないでconfigureすると/usr/local/libに入ってしまうので、一応/usr/local/lib64に入れるようにします。

$ ll /usr/local/lib64/

total 2052
-rw-r--r-- 1 root root 1315136 Aug 29 20:53 libedit.a
-rwxr-xr-x 1 root root 930 Aug 29 20:53 libedit.la
lrwxrwxrwx 1 root root 17 Aug 29 20:53 libedit.so -> libedit.so.0.0.58
lrwxrwxrwx 1 root root 17 Aug 29 20:53 libedit.so.0 -> libedit.so.0.0.58
-rwxr-xr-x 1 root root 762136 Aug 29 20:53 libedit.so.0.0.58
drwxr-xr-x 2 root root 4096 Aug 29 20:53 pkgconfig
drwxr-xr-x 3 root root 4096 Aug 11 01:26 python2.7
drwxr-xr-x 3 root root 4096 Aug 11 01:26 ruby

差し替え方法 その1

ldconfigの設定を変更する。
ただし、この方法は全てに波及してしまうので気をつける必要があります。

cat <<EOS | sudo tee /etc/ld.so.conf.d/libedit.conf
/usr/local/lib64
EOS

# キャッシュ更新
sudo ldconfig

sudo ldconfig -p | grep edit
libedit.so.0 (libc6,x86-64) => /usr/local/lib64/libedit.so.0
libedit.so.0 (libc6,x86-64) => /usr/lib64/libedit.so.0
libedit.so (libc6,x86-64) => /usr/local/lib64/libedit.so

ldd /usr/bin/mysql | grep edit
libedit.so.0 => /usr/local/lib64/libedit.so.0 (0x00007f459702f000)

問題なく日本語入力できました。

差し替え方法 その2

全体を差し替えてしまうのは怖いので、mysqlのCLIだけ差し替える場合はLD_LIBRARY_PATHで対応します。

alias mysql="LD_LIBRARY_PATH=/usr/local/lib64:$LD_LIBRARY_PATH /usr/bin/mysql"
ldd /usr/bin/mysql | grep edit
libedit.so.0 => /usr/local/lib64/libedit.so.0 (0x00007fb179bec000)

これでも問題なく日本語が入力できました。

GKEのSSL証明書を使ってGCP/GAEの証明書を更新する

  • このエントリーをはてなブックマークに追加

GKEでLet’s Encryptを使ってSSL証明書の自動更新をしています。それとは別に、GKEのIngressとして作成していないロードバランサやGAEにもSSL証明書を使用しています。

GAEは証明書の自動更新をしてくれますが、ワイルドカード証明書には対応していません。

私が作っている環境はワイルドカード証明書を利用しているので、GKEで自動更新設定しているSSL証明書をGCP/GAEにも適用してみます。

GKEでSSL証明書の自動更新の設定をする

こっちは本題ではないので、かなり簡潔に書きます。HTTPではなくDNS01で行います。また、cert-managerは0.3以上じゃないとワイルドカード証明書に対応していません。

HELM INSTALL
curl https://raw.githubusercontent.com/kubernetes/helm/master/scripts/get> get_helm.sh
chmod 700 get_helm.sh
./get_helm.sh
HELM UPDATE(既にインストール済みの場合はUpdateする)
helm init --upgrade

$HELM_HOME has been configured at /Users/tamtam180/.helm.

Tiller (the Helm server-side component) has been upgraded to the current version.
Happy Helming!
RBAC設定
kubectl create serviceaccount tiller --namespace kube-system
kubectl create clusterrolebinding tiller --clusterrole=cluster-admin --serviceaccount=kube-system:tiller
Install Tiller
helm init --upgrade --service-account tiller
cert-manager
git clone https://github.com/kubernetes/charts
cd charts
helm install --set image.tag=v0.3.2 --name cert-manager --namespace kube-system stable/cert-manager
cert-managerがCloudDNSを操作できるようにする
GCLOUD_PROJECT=my-sample-project
gcloud iam service-accounts create cert-manager --display-name "cert-manager"
gcloud projects add-iam-policy-binding ${GCLOUD_PROJECT} --member serviceAccount:cert-manager@${GCLOUD_PROJECT}.iam.gserviceaccount.com --role roles/dns.admin
service accountのkeyを作る
gcloud iam service-accounts keys create cert-manager-key.json --iam-account cert-manager@${GCLOUD_PROJECT}.iam.gserviceaccount.com
service accountをsecretに登録する
kubectl create secret generic clouddns-service-account --from-file=cert-manager-key.json=cert-manager-key.json --namespace=kube-system

ClusterIssuerを作る

clusterissuer.yaml
apiVersion: certmanager.k8s.io/v1alpha1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: admin@example.com # FIXME
privateKeySecretRef:
name: letsencrypt-prod
dns01:
providers:
- name: prod-dns
clouddns:
serviceAccountSecretRef:
name: clouddns-service-account
key: cert-manager-key.json
project: my-sample-project # FIXME
kubectl apply -f clusterissuer.yaml

Certificateを作る

ドメイン名はCloudDNSに登録している自分のドメインを指定します。
namespaceにdevを指定していますが、そこも自分の環境に合わせて調整してください。(私の場合はワイルドカード証明書を使っている関係で、dev環境やstaging環境に別のドメインを使っているため、環境ごとにnamesapceを分けています)

certificate.yaml
apiVersion: certmanager.k8s.io/v1alpha1
kind: Certificate
metadata:
name: example-com-tls
namespace: dev
spec:
secretName: example-com-tls
commonName: example.com
dnsNames:
- "example.com"
- "*.example.com"
acme:
config:
- dns01:
provider: prod-dns
domains:
- "example.com"
- "*.example.com"
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
kubectl apply -f certificate.yaml

GKEのSSL証明書をGAEに設定する

GAEは既存の証明書をupdateコマンドで一発で更新できます。

gcloud app ssl-certificates list 2>/dev/nullで証明書の一覧が取得できます。

GAEの証明書のExpire時間を計算する
CERT_ID=xxxxx # 目的のIDにしてね
T=$(LANG=C date --date $(gcloud app ssl-certificates describe ${CERT_ID} | grep expireTime | sed -e "s/'//g" | cut -d " " -f 2) +'%s')
expr \( $T - $(date +'%s') \) / 86400

こんな感じで今の証明書があと何日で期限切れになるのか計算できます。cert-managerでは30日前に自動更新するようになっているので、30日とか25日あたりを閾値にすると良いと思います。

GAEの証明書を更新する

# 秘密鍵を取得する
kubectl get secret example-com-tls -o json --namespace=dev | jq -r '.data["tls.key"]' | base64 --decode > /tmp/tls.key
# 証明書を取得する
kubectl get secret example-com-tls -o json --namespace=dev | jq -r '.data["tls.crt"]' | base64 --decode > /tmp/tls.crt

証明書の有効期限が延長済みであることを確認するには、次のコマンドで証明書の有効期限を見れば良いです。

# 有効期限の表示
cat /tmp/tls.crt | openssl x509 -noout -enddate
notAfter=Oct 29 08:40:28 2018 GMT

# UNIX時間でほしい場合
date --date "$(cat /tmp/tls.crt | openssl x509 -noout -enddate | cut -d'=' -f2)" +"%s"
CERT_ID=xxxx
gcloud app ssl-certificates update "${CERT_ID}" \
--display-name "*.example.com lets" \ # GAEに登録するSSL証明書の名前。日付とか入れると良いかも
--certificate "/tmp/tls.crt" \
--private-key "/tmp/tls.key"

ロードバランサの証明書を更新する

※GKE(k8s)のIngressとして作成したロードバランサは、cert-managerで勝手に更新されます。そうではない野良のロードバランサが対象です。

GCP管理の証明書を一覧する
# この結果にはGKEで作成したものも含まれています
gcloud compute ssl-certificates list

残念ながらGAEのようなupdateコマンドがありません。
手順はSSL証明書を作成し、target-https-proxiesにその作成した証明書を設定します。

SSL証明書の更新
CERT_NAME="star-dev-example-com-$(date +'%Y-%m-%d')"
gcloud compute ssl-certificates create "${CERT_NAME}" \
--certificate "/tmp/tls.crt" \
--private-key "/tmp/tls.key"

TARGET_PROXY="star-example-com-target-proxy-2"
gcloud compute target-https-proxies update "${TARGET_PROXY}" \
--ssl-certificates "${CERT_NAME}"

これでGKEの環境で証明書を自動更新しつつ、ほかの環境にも展開できるようになりました。実運用はk8sのcronjobを定義して↑の内容をよしなに処理するスクリプトを作れば良いと思います。