メルカリのgo.mercari.io/hcledit が良かった話
Terraformをプログラムで操作していたい
Terraformをプログラムから操作する場合は hclwriterなどのHCL自体を操作する方法がよく紹介されているがHCLを直接の操作するのはとても面倒なのでなんどかトライして挫折してしまった 今回使ってみたメルカリが公開しているgo.mercari.io/hcledit を支えば少し楽に操作ができたので 初回します
go.mercari.io/hcledit とは
メルカリが公開しているOSSでhclwriterをラッピングしてくれてるライブラリになる
package main
import (
"fmt"
"io"
"strings"
"go.mercari.io/hcledit"
)
func convertIoReader(h string) io.Reader {
return strings.NewReader(h)
}
func find_test(r io.Reader, q string) {
editor, _ := hcledit.Read(r, "dummy")
m, err := editor.Read(q)
if err != nil {
panic(err)
}
for _, v := range m {
fmt.Println(v)
}
}
func write(r io.Reader, q string, v string) {
editor, _ := hcledit.Read(r, "dummy")
err := editor.Create(q, hcledit.RawVal(v))
if err != nil {
panic(err)
}
fmt.Println(string(editor.Bytes()))
}
func update(r io.Reader, q, v string) {
editor, _ := hcledit.Read(r, "")
err := editor.Update(q, hcledit.RawVal(v))
if err != nil {
panic(err)
}
fmt.Println(string(editor.Bytes()))
}
func updateString(r io.Reader, q, v string) {
editor, _ := hcledit.Read(r, "")
err := editor.Update(q, v)
if err != nil {
panic(err)
}
fmt.Println(string(editor.Bytes()))
}
func main() {
h1 := `
locals {
s3 = {
test1 = {
test1_1 = "test1_1"
}
test2 = {
test2_1 = "test2_1"
}
}
}
`
write(convertIoReader(h1), "locals.s3.test1.test1_2", `[
{
something1 = "1"
something2 = "2"
}
]
`)
find_test(convertIoReader(h1), "locals.s3.test1.test1_1")
update(convertIoReader(h1), "locals.s3.test1.test1_1", `[{ test="test"}]`)
updateString(convertIoReader(h1), "locals.s3.test1.test1_1", "test1-1")
}
こんな感じで出力される
locals {
s3 = {
test1 = {
test1_1 = "test1_1"
test1_2 = [
{
something1 = "1"
something2 = "2"
}
]
}
test2 = {
test2_1 = "test2_1"
}
}
}
test1_1
locals {
s3 = {
test1 = {
test1_1 = [{ test = "test" }]
}
test2 = {
test2_1 = "test2_1"
}
}
}
locals {
s3 = {
test1 = {
test1_1 = "test1-1"
}
test2 = {
test2_1 = "test2_1"
}
}
}
terraform-exec の使い方
What is terraform-exec
Hashicorpにメンテナンスされているterraform cliをGolangから使うためのモジュール
GitHub - hashicorp/terraform-exec: Terraform CLI commands via Go.
How to use
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"github.com/hashicorp/go-version"
"github.com/hashicorp/hc-install/product"
"github.com/hashicorp/hc-install/releases"
"github.com/hashicorp/terraform-exec/tfexec"
)
func main() {
installer := &releases.ExactVersion{
Product: product.Terraform,
// versionは必須
Version: version.Must(version.NewVersion("1.0.6")),
}
execPath, err := installer.Install(context.Background())
if err != nil {
log.Fatalf("error installing Terraform: %s", err)
}
workingDir := "path/to/working/dir/terraform"
tf, err := tfexec.NewTerraform(workingDir, execPath)
if err != nil {
log.Fatalf("error running NewTerraform: %s", err)
}
err = tf.Init(context.Background(), tfexec.Upgrade(true))
if err != nil {
log.Fatalf("error running Init: %s", err)
}
// terraform plan -out を使う場合はPlanOptionを使う
planConfig := []tfexec.PlanOption{
tfexec.Out("./out.txt"),
}
plan, err := tf.Plan(context.Background(), planConfig...)
if err != nil {
log.Fatalf("error running plan: %s", err)
}
// 差分が有る場合はtrueになる
fmt.Println(plan)
show, err := tf.Show(context.Background())
if err != nil {
log.Fatalf("error show: %s", err)
}
b, err := json.Marshal(show)
if err != nil {
log.Fatalf("err %s", err)
}
// json形式でplan結果が表示される
fmt.Println(string(b))
// ファイル出力したバイナリを使う場合
// planStr2, err := tf.ShowPlanFile(context.Background(), "./out.txt")
// if err != nil {
// log.Fatalf("err %s", err)
// }
// b, err := json.Marshal(planStr2)
// if err != nil {
// log.Fatalf("err %s", err)
// }
// fmt.Println(string(b))
// tf.Apply(context.Background())
}
NestJSのUnitTestでsqlite3を使う方法
UnitTestの時にDBにつなぐのではなくMockデーターベース(sqlite)を起動させMockデーターベースに接続する方法
ソースコードの大半はNestJSのDatabaseの項目を参考にしています。
https://docs.nestjs.com/techniques/database
1 対象のアプリを実装していきます
アプリを作成
nest new app依存ライブラリのインストール
npm install --save @nestjs/typeorm typeorm mysql2app.moduleの更新
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'root',
database: 'test',
entities: [],
synchronize: true,
}),
],
})
export class AppModule {}
- User moduleを作成
nest g mo user nest g s user
- User entityを作成
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
firstName: string;
@Column()
lastName: string;
@Column({ default: true })
isActive: boolean;
}
- User moduleを更新
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './user.entitiy';
import { UserService } from './user.service';
@Module({
providers: [UserService],
imports: [TypeOrmModule.forFeature([User])],
})
export class UserModule {}
- UserServiceにロジックを追加
export class UserService {
constructor(
@InjectRepository(User)
private usersRepository: Repository<User>,
) {}
findAll(): Promise<User[]> {
return this.usersRepository.find();
}
findOne(id: string): Promise<User> {
return this.usersRepository.findOne(id);
}
async remove(id: string): Promise<void> {
await this.usersRepository.delete(id);
}
}
2 UnitTest
今回Mockのデータベースにsqliteを利用します
npm install --save slqite3
ちなみに今のUserServiceのテストはこのようになっています。
この状況でnpm run testを行ってもテストは通りません。
describe('UserService', () => {
let service: UserService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [UserService],
}).compile();
service = module.get<UserService>(UserService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
次のように変更するとテストが通るようになります。
providers: [
UserService,
{
provide: getRepositoryToken(User),
useClass: Repository,
},
],
describe('UserService', () => {
let service: UserService;
let repository: Repository<User>;
const testConnectionName = 'testConnection';
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UserService,
{
provide: getRepositoryToken(User),
useClass: Repository,
},
],
}).compile();
// mock用DBの作成
const connection = await createConnection({
type: 'sqlite',
database: ':memory:', // inmemoryで動かす
dropSchema: true,
entities: [User],
synchronize: true,
logging: false,
name: testConnectionName,
});
repository = getRepository(User, testConnectionName);
//
service = new UserService(repository);
});
// テストが終わる度にMockDBをクリーンにする
afterEach(async () => {
await getConnection(testConnectionName).close();
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
あとはこんな感じでテストを書くだけ
describe('find', () => {
it('find', async () => {
const user: User = {
id: 1,
firstName: 'test',
lastName: 'user',
isActive: true,
};
await repository.save(user);
const inUsers = await service.findAll();
expect(inUsers.length).toBe(1);
expect(inUsers[0].firstName).toBe(user.firstName);
});
});
今回のコード
ArgoCD NotificationsでMS Teamsに通知する方法
今回の内容
ArgoCD notificationsでMS temasに通知するときに詰まった点について記載していきます。
事前準備
ArogCDは事前"Getting start"でインストールにしていると想定しています。
ArgoCD Notificationの準備
ArgoCDのStable版(V1.0)でMS TeamsへのNotificationがサポートされいるように見えますが、実はv1.0ではサポートされていません。Notificationのテンプレートを確認するとEmailとslackはサポートしていますがMS Teamsのテンプレートが含まれていません。
なのでv.1.1.0を使いましょう

Install ArgoCD Notification and Template
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj-labs/argocd-notifications/v1.1.0/manifests/install.yaml kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj-labs/argocd-notifications/v1.1.0/catalog/install.yaml
通知の設定
Setp:
1 argocd-notifications-secret.yamlを作成
apiVersion: v1 kind: Secret metadata: name: argocd-notifications-secret stringData: channel-teams-url: <your-webhook-url>
kubectl appy -f argocd-notifications-secret.yaml
2 argocd-notifications-cmを更新
kubectl edit configmap argocd-notifications-cm -n argocd
Dataの下に追加
data:
service.teams: |
recipientUrls:
channelName: $channel-teams-url
3 パイプライン一覧を確認
$ kubectl get Application -n argocd NAME SYNC STATUS HEALTH STATUS test OutOfSync Healthy
4 アノテーションを追加
$ kubectl edit Application test -n argocd
metadata:
annotations:
notifications.argoproj.io/subscribe.on-sync-succeeded.teams: channelName
これでArgoCDがSyncしたときにMS Teamsに通知ができるようになる 通知のタイミングはドキュメントを確認
JVM error test
Kubernetes上で動いているJavaアプリがOOMなどで死んだときに備えてどのようのエラーログを取得するかテストする方法 テスト環境はMinikube上で動かしています。 Minikube: Java version:
今回のベースアプリ
まずDeploymentを作成しDeployします。
apiVersion: apps/v1
kind: Deployment
metadata:
name: hellojava
spec:
replicas: 1
selector:
matchLabels:
name: hellojava
template:
metadata:
labels:
name: hellojava
spec:
containers:
- name: hellojava
image: mo053/gs-spring-boot-docker:latest
resources:
requests:
cpu: 100m
memory: 100Mi
env:
ports:
- containerPort: 8080
kubectl apply -f deployment -n <namespace>
STATUSがRunningになっているのでok
kubectl get pod NAME READY STATUS RESTARTS AGE hellojava-78cffc5867-z8sz5 1/1 Running 3 (12m ago) 18m
PODの中に入りプロセスを落とす
kubectl exec -it hellojava-78cffc5867-z8sz5 /bin/bash ps aux | grep java root 1 0.7 1.1 6653628 184488 ? Ssl 09:24 0:05 java -jar /app.jar $JAVA_OPTS root 54 0.0 0.0 6308 728 pts/0 S+ 09:37 0:00 grep --color=auto java kill -SIGSEGV 1
Podのログは以下のように表示される
# # A fatal error has been detected by the Java Runtime Environment: # # SIGSEGV (0xb) at pc=0x00007fd124332cd5 (sent by kill), pid=1, tid=1 # # JRE version: OpenJDK Runtime Environment Microsoft-27990 (11.0.13+8) (build 11.0.13+8-LTS) # Java VM: OpenJDK 64-Bit Server VM Microsoft-27990 (11.0.13+8-LTS, mixed mode, sharing, tiered, compressed oops, serial gc, linux-amd64) # Problematic frame: # C [libpthread.so.0+0xacd5] __pthread_clockjoin_ex+0x255 # # Core dump will be written. Default location: Core dumps may be processed with "/usr/share/apport/apport %p %s %c %d %P %E" (or dumping to //core.1) # # An error report file with more information is saved as: # //hs_err_pid1.log # # If you would like to submit a bug report, please visit: # https://github.com/microsoft/openjdk/issues # [error occurred during error reporting (), id 0xb, SIGSEGV (0xb) at pc=0x00007fd124143941]
Deploymentを次のように更新すると/dumpにhs_error_pidが吐かれるようになる
apiVersion: apps/v1
kind: Deployment
metadata:
name: hellojava
spec:
replicas: 1
selector:
matchLabels:
name: hellojava
template:
metadata:
labels:
name: hellojava
spec:
containers:
- name: hellojava
image: mo053/gs-spring-boot-docker:latest
resources:
requests:
cpu: 100m
memory: 100Mi
env:
- name: JAVA_TOOL_OPTIONS
value: "-XX:ErrorFile=/dump/error.log -Xmx50m -XX:+CrashOnOutOfMemoryError -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/dump/oom.bin"
ports:
- containerPort: 8080
volumeMounts:
- mountPath: /dump
name: dump-volume
volumes:
- name: dump-volume
emptyDir: {}
OOMを発生させる方法
OOMを疑似的に発生させヒープダンプが正しく出されるか検証
コードはすべてここに置いています。
- Spring bootのwebプロジェクトを作成
- OOMのタイミングをコントロールするために適当なコントローラーを作成
こんな感じ
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/hello")
public class HelloController {
@RequestMapping(method= RequestMethod.GET)
public String hello() {
return "Hello Spring MVC";
}
}
次のメソッドを追加
@RequestMapping(value="/test", method=RequestMethod.GET)
public void test() {
Object[] o = null; while (true) { o = new Object[] {o}; }
}
補足 OutOfMemoryErrorをthrowするのではヒープダンプは出さない
@RequestMapping(value="/test", method=RequestMethod.GET)
public void test() {
throw new OutOfMemoryError();
}
- アプリケーション起動時にXmxを小さくする
java -Xmx50m -XX:+CrashOnOutOfMemoryError -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/dump/oom.bin -jar target/demo-0.0.1-SNAPSHOT.jar
** 制限しない場合はPCのメモリを使い果たすまでメモリを消費するので注意
`