TerraformでHelmのReleaseを管理して、JenkinsをMinikube上に構築してみる
概要
- TerraformでHelmのReleaseを管理できるようなので、やってみる
gist
御託はいいので、terraform applyだけさせろ!派の方はgistに最終的な構成を置いてるので、こちらをご参照ください。
ただし、前準備が必要なのは、変わらないのでご注意ください。
こういう用途で参考になるかも?
- 複数のHelm Releaseの構成管理をterraformでまとめて or 分けて管理できる
- Minikube,EKSやGKE等のKubernetesクラスタ自体の構成とHelmの構成をまとめて or 分けて管理できる
Helm Providerについて
Hashicorp公式のHelm Providerが存在します。
また、公式のチュートリアルページも用意されています。
今回はJenkins用のHelm chartを使わせてもらい, Minikube上にJenkinsを構築してみます。
動作環境構築
動作の前提条件としてHelmとMinikubeの動作環境構築及び、JenkinsのHelmリポジトリの追加作業が必要なのでそれぞれ準備します
Minikube
Helm
Jenkinsのリポジトリを追加
下記コマンドを実行
helm repo add jenkins https://charts.jenkins.io
.tfファイルを書いていく
まずは簡単なtfファイルを書いて、動作確認してみます。
providerの設定
providerの設定を記載していきます。
backendやproviderのバージョン等の情報はお手元で変更の必要があれば、適正変更してください。
terraform {
backend "local" {}
required_providers {
helm{
source = "hashicorp/helm"
version = "~= 2.6.0"
}
}
}
provider "helm" {
kubernetes {
config_path = "~/.kube/config"
}
}
もし、すでに複数のconfigがあるような場合(すでにいくつもクラスタを作成してるようなケース)は以下が参考になります。
helm_releaseの構成を作成して、動作確認してみる
「resource helm_release」を作成する事で対応するHelmのReleaseを作成する事ができます。
今回使用させて頂くJenkins Chartのレンダリングに使用される変数の種類,用途,デフォルト値等の情報は以下ページに詳細にまとめられているので、一旦これを確認してみます。
とりあえず、一旦動かしてminikube内で動作するJenkinsのコンソールにログインできることを確認したいので、
下記の設定のみ変更します
- 「controller.serviceType」
今回はminikubeで動かす都合上、デフォルトの「ClusterIP」で動かれても外部(この場合はminikubeを動作させているホストPC)
からアクセスできないので、これを「NodePort」に指定します。
SUMMARY.mdの方には記載されていないですが、values.yamlの方にはminikubeに関する言及が記載されています
https://github.com/jenkinsci/helm-charts/blob/main/charts/jenkins/values.yamlFor minikube, set this to NodePort, elsewhere use LoadBalancer
Use ClusterIP if your setup includes ingress controller
serviceType: ClusterIP
Releaseを作成する
「variables.tf_varsに切り分けた方がいい」、「Helmのvalues.yamlに切り分けた方がいい」等の考えは一旦置いておき、直接必要な情報を書いて動かしてみます。
resource "helm_release" "jenkins" {
name = "jenkins-k8s-example"
chart = "jenkins/jenkins"
namespace = "jenkins"
version = "4.2.5"
create_namespace = true
set {
name = "controller.serviceType"
value = "NodePort"
}
}
この状態でterraform applyしてみます。
特に問題がなければ、NamespaceやPod,Serviceが作成されているはずなので、確認してみます。
>kubectl get ns jenkins
NAME STATUS AGE
jenkins Active 6m35s
>kubectl get pod -n jenkins
NAME READY STATUS RESTARTS AGE
jenkins-k8s-example-0 2/2 Running 0 7m3s
>kubectl get svc -n jenkins
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
jenkins-k8s-example NodePort 10.107.0.117 <none> 8080:32488/TCP 7m23s
jenkins-k8s-example-agent ClusterIP 10.101.219.9 <none> 50000/TCP 7m23s
問題なさそうなので、次は直接アクセスJenkinsのダッシュボードにログインして確認してみます。
Jenkinsのダッシュボードにログインする
- adminアカウントのパスワードを確認する
Jenkinsのadminアカウントはデフォルトではランダムにパスワードが設定されおり
Helm Relaseと同名のSecretにパスワードが保存されているので、ログインする為にこれを確認します
kubectl get secrets -n jenkins
NAME TYPE DATA AGE
jenkins-k8s-example Opaque 2 16m
sh.helm.release.v1.jenkins-k8s-example.v1 helm.sh/release.v1 1 16m
中身を確認します。
kubectl describe secrets jenkins-k8s-example -n jenkins
Name: jenkins-k8s-example
Namespace: jenkins
Labels: ~~~~
Annotations: ~~~~
Type: Opaque
Data
====
jenkins-admin-password: 22 bytes
jenkins-admin-user: 5 bytes
dataの部分だけ抜き出します。
kubectl get secrets jenkins-k8s-example -o jsonpath='{.data}'
{"jenkins-admin-password":"V3Y2NExWS1FnTEZUMUZ6VXZzazdYdw==","jenkins-admin-user":"YWRtaW4="}
base64エンコードされているのでデコードします。
echo 'V3Y2NExWS1FnTEZUMUZ6VXZzazdYdw==' | base64 -d
->
Wv64LVKQgLFT1FzUvsk7Xw
これが設定されているadminアカウントのパスワードです。
これを使用してログインしてみます。
ダッシュボードにアクセスしてログイン
kubectl get svcでも確認できましたが、NodePortで解放されているServiceを経由して
Jenkinsにアクセスする為のURLを取得します。
今回の構成だと以下のコマンドで確認する事ができます。
minikube service jenkins-k8s-example --url -n jenkins
http://192.168.118.146:32488
出力されたURLにブラウザからアクセスする事で、Jenkinsのダッシュボードにアクセスする事ができます。
あとはadminアカウントを使用して、上記で得たパスワードを入力する事でJenkinsダッシュボードにログインする事ができます。
set をまとめる
パスワード固定したいからcontroller.adminPassword
も固定で設定しておくか~
みたいな感じで構築する環境毎にsetする値が増えていく事が一般的かなと思います。
そういうケースでは「Terraformの層でsetをまとめる」「Helmの層でvaluesとしてまとめる」の方法が使えるかと思うので、両方書いてみます。
resource "helm_release" "jenkins" {
name = "jenkins-k8s-example"
~~~~
set {
name = "controller.serviceType"
value = "NodePort"
}
set {
name = "controller.adminPassword"
value = "Wv64LVKQgLFT1FzUvsk7Xw"
}
set {
~~~~
}
~~~
}
Terraformの層でsetをまとめる
setがだらだらresourceブロックの中に連なると、長ったらしくなるのでdynamic Blocksでまとめてみると以下のような形になります。
もちろん、variable自体を**.tfvarsに分けて、別ファイルに切り出す事も可能です。
variable "helm_values" {
type = list(list(string))
default = [
["controller.serviceType", "NodePort"],
["controller.adminSecret", "true"],
["controller.adminPassword", "Wv64LVKQgLFT1FzUvsk7Xw"],
]
}
resource "helm_release" "jenkins" {
name = "jenkins-k8s-example"
chart = "jenkins/jenkins"
namespace = "jenkins"
version = "4.2.5"
create_namespace = true
dynamic "set" {
for_each = var.helm_values
content {
name = set.value[0]
value = set.value[1]
}
}
}
Helmの層でvaluesとしてまとめる
こんな感じのyamlを用意して
controller:
serviceType: ClusterIP
adminSecret: true
adminPassword: Wv64LVKQgLFT1FzUvsk7Xw
variable "helm_values_file" {
type = string
default = "jenkins_minikube_values.yaml"
}
resource "helm_release" "jenkins" {
name = "jenkins-k8s-example"
chart = "jenkins/jenkins"
namespace = "jenkins"
version = "4.2.5"
create_namespace = true
values = [
file(var.helm_values_file)
]
}
という定義でも、同様の構成が構築されます。
両方併用すると?
以下のようなケースで「terraform apply」すると
setとvaluesで同じ変数を指定した場合「set」で指定した値の方が優先されます
両方が使用される場合、--set 値はより高い優先度で --values にマージされます。
↓の例でいうとcontroller.serviceTypeはNodePortになります
controller:
serviceType: ClusterIP
variable "helm_values" {
type = list(list(string))
default = [
["controller.serviceType", "NodePort"],
]
}
variable "helm_values_file" {
type = string
default = "jenkins_minikube_values.yaml"
}
resource "helm_release" "jenkins" {
name = "jenkins-k8s-example"
chart = "jenkins/jenkins"
namespace = "jenkins"
version = "4.2.5"
create_namespace = true
values = [
file(var.helm_values_file)
]
dynamic "set" {
for_each = var.helm_values
content {
name = set.value[0]
value = set.value[1]
}
}
}
helm_templateの出力について
setにしろvaluesにしろ、業務や開発で利用していくと扱うChartの数も増え、渡す設定値も増加する事が懸念されます。
その為、Helmが最終的にレンダリングしたyamlを確認する手段、つまり「helm template」コマンド相当の出力を、terraform applyする前に確認したいという要望があるかなと思います。
上記の確認にはdata helm_template
を利用できます。
helm_releaseに渡す値と変数を渡して置くことで、dataリソースとして利用できるため
「output定義追加しておいて、terraform planでレンダリングされたマニフェスト(yaml)を確認する」
「ほかの定義、たとえばresourceブロックで参照する」
といった用途に利用する事ができます
variable "helm_values" {
type = list(list(string))
default = [
["controller.serviceType", "NodePort"],
["controller.adminSecret", "true"],
["controller.adminPassword", "Wv64LVKQgLFT1FzUvsk7Xw"],
]
}
data "helm_template" "jenkins" {
name = "jenkins-k8s-example"
chart = "jenkins/jenkins"
namespace = "jenkins"
version = "4.2.5"
dynamic "set" {
for_each = var.helm_values
content {
name = set.value[0]
value = set.value[1]
}
}
}
output "jenkins_manifests" {
value = data.helm_template.jenkins.manifests
}
resource "helm_release" "jenkins" {
name = data.helm_template.jenkins.name
chart = data.helm_template.jenkins.chart
namespace = data.helm_template.jenkins.namespace
version = data.helm_template.jenkins.version
create_namespace = true
dynamic "set" {
for_each = var.helm_values
content {
name = set.value[0]
value = set.value[1]
}
}
}
出力例
terraform plan
data.helm_template.jenkins: Reading...
data.helm_template.jenkins: Read complete after 2s [id=jenkins-k8s-example]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated
with the following symbols:
- create
Terraform will perform the following actions:
~~省略
Changes to Outputs:
- jenkins_manifests = {
- "templates/config.yaml" = <<-EOT
---
# Source: jenkins/templates/config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: jenkins-k8s-example
namespace: jenkins
labels:
"app.kubernetes.io/name": 'jenkins'
"app.kubernetes.io/managed-by": "Helm"
"app.kubernetes.io/instance": "jenkins-k8s-example"
"app.kubernetes.io/component": "jenkins-controller"
data:
~~~~~~~~~~~~~~~~~~~~
EOT - "templates/home-pvc.yaml" = <<-EOT
---
# Source: jenkins/templates/home-pvc.yaml
kind: PersistentVolumeClaim
apiVersion: v1
~~~~~~~~~~~~~~~~~~~~
- "templates/config.yaml" = <<-EOT
所感
今回はMinikubeで環境構築してますが、本来この構成は他のインフラ構成もまとめて構成管理に含めてしまえる事が
最大のメリットなので、そのうちEKSやGKEでも試してみるかもしれません。
💪😀🗒️⚓「イッツマイyaaaaml」!
💪😁「HA!」
😶「適用するnamespace間違えました!」
😇
Discussion