やりかけメモ
func injectEnv(c AppConfig) AppConfig { injectEnv2(&c) return c } func injectEnv2(c interface{}) { fmt.Println(">>") tmpC := c if reflect.TypeOf(c).Kind() == reflect.Ptr { tmpC = reflect.Indirect(reflect.ValueOf(c)).Interface() } t := reflect.TypeOf(tmpC) v := reflect.ValueOf(tmpC) for i := 0; i < t.NumField(); i++ { f := t.Field(i) fv := v.Field(i) fmt.Println("===") fmt.Printf("Name: %s\n", f.Name) fmt.Printf("Type: %s(%s)\n", f.Type, f.Type.Kind()) switch f.Type.Kind() { case reflect.Struct: injectEnv2(fv.Interface()) break default: break } } fmt.Println("<<") }
gRPC over SSL/TLS with ClientCertification
gRPCを利用したサービスを作ることになったので、その際に調べた認証・認可周りのメモ
キーワード:grpc, grpcs, client認証
gRPCにおける認証方法
gRPCの認証は公式サイトによると以下の方式がビルトインされてるらしい
また、認証情報は以下の方式で設定できる
- Channel credential
- SSL証明書などのように
gRPC Channel
に紐付ける方式
- SSL証明書などのように
- Call credential
- Request時にClientContextに対して設定する方式
今回はgRPCServiceをグローバルに公開する予定があったのと通信を暗号化したかったので、クライアントに自己署名証明書(通称、オレオレ証明書)を払い出して、その証明書のCommonNameで認可を行う SSL/TLS + Channel credential
の方式で試してみた。
[利用方法のイメージ] +------- Internet ---------+ +---------+ | | | Client | -------(gRPC over SSL/TLS)---------> [gRPCService] +---------+ | ( with ClientCert ) | :443 +--------------------------+
準備
オレオレ認証局
クライアントに払い出す証明書を生成する必要があるので、オレオレ認証局を オレオレ証明書の発行はこちらのQiita記事を参考にキーの出力フォーマットをなどを調整したものをGithubに上げた
https://github.com/tmarcus87/grpc-training-self-signed-ca
クライアントコード
Serverが1台の場合
以下のような感じでSSLで待ち受けるサーバを実装して起動すると動くはず
Serverが複数台の場合
公式のドキュメントによるとLoadBalancingは以下の3種の方法が提案されている
- Proxy方式
- 負荷分散に対応したClientを利用する方式 GoとJavaだとそういう対応クライアントがあるらしい(未検証)
- 外部のLoadBalcningServiceを利用する方式
- ちゃんと読んでないけど、EtcdとかZooKeeperとかEurekaとか使ってDNS応答を変化させてやる方式?
Proxy以外の方式では1IP:1Serverである必要があるっぽい?
Proxy方式でnginxを利用する場合以下の選択肢がある
- L4 load balancing
- L7 load balancing
// todo Plain接続の場合のアプリ側のコードを書く
// todo L4 Load balancingの場合のnginx.confを貼り付ける
L7 Load balancingの場合のnginx.confは以下の通り
[nginxの設定] $ cat /etc/nginx/nginx.conf user nginx; worker_processes 1; error_log /var/log/nginx/error.log debug; pid /var/run/nginx.pid; events { worker_connections 1024; } http { upstream grpc_servers { server grpc_server1:5000; server grpc_server2:5000; } map $http_upgrade $connection_upgrade { default upgrade; '' close; } server { listen 443 ssl http2; ssl_certificate /opt/server/dev-gateway-api.example.com.crt; ssl_certificate_key /opt/server/dev-gateway-api.example.com.pem; ssl_client_certificate /opt/ca/ca.crt; ssl_verify_client on; proxy_set_header ssl-client-cert $ssl_client_escaped_cert; proxy_set_header ssl-client-verify $ssl_client_verify; proxy_set_header ssl-client-subject-dn $ssl_client_s_dn; proxy_set_header ssl-client-issuer-dn $ssl_client_i_dn; grpc_set_header ssl-client-cert $ssl_client_escaped_cert; grpc_set_header ssl-client-verify $ssl_client_verify; grpc_set_header ssl-client-subject-dn $ssl_client_s_dn; grpc_set_header ssl-client-issuer-dn $ssl_client_i_dn; location /error502grpc { internal; default_type application/grpc; add_header grpc-status 14; add_header grpc-message "unavailable"; return 204; } location / { grpc_pass grpc://grpc_servers; error_page 502 = /error502grpc; } } }
サーバ側で以下のデータがmetadataから取れる
こんな感じの関数をUnaryServerInterceptorに差し込めば func getMetaFromHeader(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { if md, ok := metadata.FromIncomingContext(ctx); ok { json, _ := json.Marshal(md) fmt.Println(string(json)) } return handler(ctx, req) } こんなデータが取れるはず { ":authority": [ "grpc_servers" ], "content-type": [ "application/grpc" ], "ssl-client-cert": [ "-----BEGIN%20CERTIFICATE-----なんやかんや証明書の中身%0A-----END%20CERTIFICATE-----%0A" ], "ssl-client-issuer-dn": [ "emailAddress=grpc@example.com,CN=gateway-gateway.example.com,OU=DevelopmentHQ,O=Hoge\\,Inc.,L=Sumida-ku,ST=Tokyo,C=JP" ], "ssl-client-subject-dn": [ "emailAddress=service1@example.com,CN=service1,OU=DevelopmentHQ,O=Hoge\\,Inc.,L=Sumida-ku,ST=Tokyo,C=JP" ], "ssl-client-verify": [ "SUCCESS" ], "user-agent": [ "grpc-go/1.15.0" ] }
MySQL8を入れて、memcached pluginを使うための忘備録
$ yum install -y https://dev.mysql.com/get/mysql80-community-release-el7-1.noarch.rpm $ yum install -y mysql-community-server mysql-community-client $ systemctl start mysqld $ grep "password" /var/log/mysqld.log $ mysql -uroot -p
rootで入ったらパスワードリセット
mysql> set password = 'P4$$w0rd';
mysql 5.7.6でpassword()でラップしなくて良くなったので
設定投入
mysql> create database innodb_memcache; mysql> use innodb_memcache; -- キャッシュポリシーの設定 mysql> CREATE TABLE IF NOT EXISTS `cache_policies` ( `policy_name` VARCHAR(40) PRIMARY KEY, `get_policy` ENUM('innodb_only', 'cache_only', 'caching','disabled') NOT NULL , `set_policy` ENUM('innodb_only', 'cache_only','caching','disabled') NOT NULL , `delete_policy` ENUM('innodb_only', 'cache_only', 'caching','disabled') NOT NULL, `flush_policy` ENUM('innodb_only', 'cache_only', 'caching','disabled') NOT NULL ) ENGINE = innodb; mysql> INSERT INTO cache_policies VALUES("cache_policy", "innodb_only", "innodb_only", "innodb_only", "innodb_only"); -- テーブル/カラムとのマッピングを追加 mysql> CREATE TABLE IF NOT EXISTS `containers` ( `name` varchar(50) not null primary key, `db_schema` VARCHAR(250) NOT NULL, `db_table` VARCHAR(250) NOT NULL, `key_columns` VARCHAR(250) NOT NULL, `value_columns` VARCHAR(250), `flags` VARCHAR(250) NOT NULL DEFAULT "0", `cas_column` VARCHAR(250), `expire_time_column` VARCHAR(250), `unique_idx_name_on_key` VARCHAR(250) NOT NULL ) ENGINE = InnoDB; mysql> INSERT INTO containers VALUES ("aaa", "test", "demo_test", "key", "value", "flags", "cas", "expire", "PRIMARY"); -- 設定を追加 mysql> CREATE TABLE IF NOT EXISTS `config_options` ( `name` varchar(50) not null primary key, `value` varchar(50) ) ENGINE = InnoDB; mysql> INSERT INTO config_options VALUES("separator", "|"); mysql> INSERT INTO config_options VALUES("table_map_delimiter", ".");
テストデータの投入
mysql> create database test; mysql> use test; mysql> CREATE TABLE demo_test ( `key` VARCHAR(32) not null primary key, `value` VARCHAR(1024), `flags` INT, `cas` BIGINT UNSIGNED, `expire` INT ) ENGINE = InnoDB; mysql> INSERT INTO demo_test VALUES ("AA", "HELLO, HELLO", 8, 0, 0);
memcached pluginのインストール
mysql> install plugin daemon_memcached soname "libmemcached.so";
さすれば、11211でLISTENしてるので
$ netstat -ant Active Internet connections (servers and established) Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 0 0.0.0.0:11211 0.0.0.0:* LISTEN : : :
telnetでアクセスして試してみる
$ telnet localhost 11211 Trying ::1... Connected to localhost. Escape character is '^]'. get AA VALUE AA 8 12 HELLO, HELLO END quit Connection closed by foreign host.
Spring + JUnitでParameterized実行する
上記のQiita記事通りにやっても動かなかったので…
以下のような感じで、@ClassRuleと@Ruleを宣言してやれば無事動いた
package org.tmarcus import org.junit.ClassRule; import org.junit.Rule; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.context.annotation.ComponentScan; import org.springframework.test.context.junit4.rules.SpringClassRule; import org.springframework.test.context.junit4.rules.SpringMethodRule; @ActiveProfiles("test") @SPringApplicationConfiguration(ParameterizedTestBase.class) @EnableAutoConfiguration @ComponentScan(basePackages = { "org.tmarcus" }) @RunWith(Parameterized.class) public ParameterizedTestBase extends TestBase { @ClassRule public static final SpringClassRule SCR = new SpringClassRule(); @Rule public final SpringMethodRule springMethodRule = new SpringMethodRule(); }
package org.tmarcus import lombok.extern.slf4j.Slf4j; import org.junit.Test; import org.junit.runners.Parameterized; import org.tmarcus.service.MathmaticsService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.TestContextManager; import java.util.Collection; import java.util.List; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; @Slf4j public class SampleTest extends ParameterizedTestBase { private MathmaticsService service; @Autowired public void inject( MathmaticsService service) { this.service = service; } @Parameterized.Parameters public static Collection<Object[]> data() { return Arrays.asList(new Object[][] { { 0, 1 }, { 1, 1 }, { 2, 2 }, { 3, 6 }, { 4, 24 }, { 5, 120 } }); } @Parameterized.Parameter(0) // <- index=0のパラメータを代入する public int parameter; @Parameterized.Parameter(1) // <- index=1のパラメータを代入する public int factorialExpected; @Test public void test() { log.info("{} -> {}", parameter, expected); assertThat(service.factorial(parameter), is(factorialExpected)); } }
RabbitMQでHAを利用したときにClientで例外を吐いてしまう問題
RabbitMQでClusterを組み、ミラーリングを設定して、いざクライアントからデータを流そうとしたら、下記のようなエラーが出てキューを開けなかったので、その対応方法の覚書。
Caused by: com.rabbitmq.client.ShutdownSignalException: channel error; protocol method: #method<channel.close>(reply-code=406, reply-text=PRECONDITION_FAILED - inequivalent arg 'x-ha-policy'for queue 'queue_name01ha' in vhost '/': received none but current is the value 'all' of type 'longstr', class-id=50, method-id=10) at com.rabbitmq.utility.ValueOrException.getValue(ValueOrException.java:67) at com.rabbitmq.utility.BlockingValueOrException.uninterruptibleGetValue(BlockingValueOrException.java:33) at com.rabbitmq.client.impl.AMQChannel$BlockingRpcContinuation.getReply(AMQChannel.java:361) at com.rabbitmq.client.impl.AMQChannel.privateRpc(AMQChannel.java:226) at com.rabbitmq.client.impl.AMQChannel.exnWrappingRpc(AMQChannel.java:118) ... 67 common frames omitted Caused by: com.rabbitmq.client.ShutdownSignalException: channel error; protocol method: #method<channel.close>(reply-code=406, reply-text=PRECONDITION_FAILED - inequivalent arg 'x-ha-policy'for queue 'queue_name01ha' in vhost '/': received none but current is the value 'all' of type 'longstr', class-id=50, method-id=10) at com.rabbitmq.client.impl.ChannelN.asyncShutdown(ChannelN.java:484) at com.rabbitmq.client.impl.ChannelN.processAsync(ChannelN.java:321) at com.rabbitmq.client.impl.AMQChannel.handleCompleteInboundCommand(AMQChannel.java:144) at com.rabbitmq.client.impl.AMQChannel.handleFrame(AMQChannel.java:91) at com.rabbitmq.client.impl.AMQConnection$MainLoop.run(AMQConnection.java:560) ... 1 common frames omitted
Channelでキューを宣言するときに、このメソッドの第六引数で、以下の通りHAを使うという設定を入れてやればOK。
Connection connection = connectionFactory.getConnection(); Channel channel = connection.createChannel(); Map<String, Object> arguments = Maps.newHashMap(); arguments.put("x-ha-policy", "all"); channel.queueDeclare("queue_name01ha", true, true, false, false, arguments);
SpringMVCのMockMvcでurlVariablesを使う方法
SpringMVCのテストを書くときによく使うMockMvcで指定するgetやらpostやらでurlTemplate内でurlVarsをどう指定すればドキュメントが見当たらなかったので、ソースに潜った結果、どうすればいいかの覚書
第2引数をindex=0として{index}という形で書いてやれば置換される模様
例)
server.perform(get("/user/{0}/{1}/setting/categories", idType.getCode(), id) .header(HttpHeaders.CONTENT_TYPE, MEDIA_TYPE_APPLICATION_JSON)) .andExpect(status().isOk()) server.perform(post("/user/{0}/{1}/setting/category/{2}", idType.getCode(), id, CATEGORY1) .header(HttpHeaders.CONTENT_TYPE, MEDIA_TYPE_APPLICATION_JSON)) .andExpect(status().isOk())