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 mysql2
app.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のメモリを使い果たすまでメモリを消費するので注意
`
Nginx-ingressでX-Forwarded-Protoの設定方法
Ingressのannotationsにconfiguration-snippetを追加する
annotations: nginx.ingress.kubernetes.io/configuration-snippet: | proxy_set_header X-Forwarded-Port 443; proxy_set_header X-Forwarded-Proto "https";
Configmap(ingress-nginx-controller)に以下を追加
data: use-forwarded-headers: 'true'
これだけではHeaderの中がこのようになる
"x-forwarded-for":"xxx.xxx.xxx.xxx","x-forwarded-host":"xxxxxx","x-forwarded-proto":"http, https","x-scheme":"http","x-forwarded-port":"80, 443",
x-forwarded-protoとx-forwarded-portを"https"と'443'のみにするにはNginx.tmplの変更が必要である。
Nginx.tmplの変更手順は以下の通りである
1 動いているnginx-ingessからnginx.tmplをエクスポートする
kubectl exec -it ingress-nginx-controller-5fd6cdcf78-qht8p -n ingress-nginx cat /etc/nginx/template/nginx.tmpl > nginx.tmpl
2 nginx.tmplから次を削除する
{{ $proxySetHeader }} X-Forwarded-Host $best_http_host; {{ $proxySetHeader }} X-Forwarded-Proto $pass_access_scheme;
3 Configmapを作成する
kubectl create configmap nginx-template --from-file=nginx.tmpl
- Nginx ingressのデプロイメントに次の項目を追加
volumeMounts: - mountPath: /etc/nginx/template name: nginx-template-volume readOnly: true volumes: - name: nginx-template-volume configMap: name: nginx-template items: - key: nginx.tmpl path: nginx.tmpl