Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wait for kubernetes API to be ready during EKS cluster creation #11426

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 59 additions & 3 deletions aws/resource_aws_eks_cluster.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
package aws

import (
"crypto/tls"
"crypto/x509"
"encoding/base64"
"fmt"
"log"
"net/http"
"net/url"
"path"
"strings"
"time"

Expand Down Expand Up @@ -161,6 +167,11 @@ func resourceAwsEksCluster() *schema.Resource {
},
Set: schema.HashString,
},
"wait_for_endpoint": {
Type: schema.TypeBool,
Optional: true,
Default: false,
},
},
}
}
Expand Down Expand Up @@ -224,7 +235,7 @@ func resourceAwsEksClusterCreate(d *schema.ResourceData, meta interface{}) error
Pending: []string{eks.ClusterStatusCreating},
Target: []string{eks.ClusterStatusActive},
Timeout: d.Timeout(schema.TimeoutCreate),
Refresh: refreshEksClusterStatus(conn, name),
Refresh: refreshEksClusterStatus(conn, name, d.Get("wait_for_endpoint").(bool)),
}
_, err = stateConf.WaitForState()
if err != nil {
Expand Down Expand Up @@ -539,7 +550,7 @@ func flattenEksEnabledLogTypes(logging *eks.Logging) *schema.Set {
return flattenStringSet(enabledLogTypes)
}

func refreshEksClusterStatus(conn *eks.EKS, clusterName string) resource.StateRefreshFunc {
func refreshEksClusterStatus(conn *eks.EKS, clusterName string, shouldWaitForEndpoint bool) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
output, err := conn.DescribeCluster(&eks.DescribeClusterInput{
Name: aws.String(clusterName),
Expand All @@ -551,6 +562,12 @@ func refreshEksClusterStatus(conn *eks.EKS, clusterName string) resource.StateRe
if cluster == nil {
return cluster, "", fmt.Errorf("EKS Cluster (%s) missing", clusterName)
}
if shouldWaitForEndpoint && aws.StringValue(cluster.Status) == eks.ClusterStatusActive {
if !isEndpointHealthzOK(aws.StringValue(cluster.Endpoint), cluster.CertificateAuthority) {
log.Printf("[DEBUG] EKS Endpoint is not ready")
return cluster, eks.ClusterStatusCreating, nil
}
}
return cluster, aws.StringValue(cluster.Status), nil
}
}
Expand Down Expand Up @@ -584,7 +601,7 @@ func waitForDeleteEksCluster(conn *eks.EKS, clusterName string, timeout time.Dur
},
Target: []string{""},
Timeout: timeout,
Refresh: refreshEksClusterStatus(conn, clusterName),
Refresh: refreshEksClusterStatus(conn, clusterName, false),
}
cluster, err := stateConf.WaitForState()
if err != nil {
Expand Down Expand Up @@ -633,3 +650,42 @@ func waitForUpdateEksCluster(conn *eks.EKS, clusterName, updateID string, timeou

return fmt.Errorf("EKS Cluster (%s) update (%s) status (%s) not successful: Errors:\n%s", clusterName, updateID, aws.StringValue(update.Status), strings.Join(detailedErrors, "\n"))
}

func isEndpointHealthzOK(endpoint string, certificate *eks.Certificate) bool {
if endpoint == "" || certificate == nil {
return false
}

u, _ := url.Parse(endpoint)
u.Path = path.Join(u.Path, "healthz")
healthzEndpoint := u.String()
caCert, _ := base64.StdEncoding.DecodeString(aws.StringValue(certificate.Data))
log.Printf("[DEBUG] Getting EKS Endpoint %s", healthzEndpoint)

caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)

tr := &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: caCertPool,
InsecureSkipVerify: false,
},
}
client := &http.Client{
Timeout: 2 * time.Second,
Transport: tr,
}
resp, err := client.Get(healthzEndpoint)

if err != nil {
log.Printf("[WARN] Error wile getting EKS Endpoint healthz: %s", err)
return false
}

if resp.StatusCode >= 200 && resp.StatusCode <= 299 {
log.Printf("[DEBUG] Getted EKS Endpoint %s successfully", healthzEndpoint)
return true
}

return false
}
5 changes: 3 additions & 2 deletions website/docs/r/eks_cluster.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ output "kubeconfig-certificate-authority-data" {
```hcl
resource "aws_iam_role" "example" {
name = "eks-cluster-example"

assume_role_policy = <<POLICY
{
"Version": "2012-10-17",
Expand Down Expand Up @@ -155,6 +155,7 @@ The following arguments are supported:
* `enabled_cluster_log_types` - (Optional) A list of the desired control plane logging to enable. For more information, see [Amazon EKS Control Plane Logging](https://docs.aws.amazon.com/eks/latest/userguide/control-plane-logs.html)
* `tags` - (Optional) Key-value mapping of resource tags.
* `version` – (Optional) Desired Kubernetes master version. If you do not specify a value, the latest available version at resource creation is used and no upgrades will occur except those automatically triggered by EKS. The value must be configured and increased to upgrade the version when desired. Downgrades are not supported by EKS.
* `wait_for_endpoint` - (Optional) Wait for the Kubernetes Endpoint to be healthy.

### vpc_config

Expand All @@ -176,7 +177,7 @@ In addition to all arguments above, the following attributes are exported:
* `oidc` - Nested attribute containing [OpenID Connect](https://openid.net/connect/) identity provider information for the cluster.
* `issuer` - Issuer URL for the OpenID Connect identity provider.
* `platform_version` - The platform version for the cluster.
* `status` - The status of the EKS cluster. One of `CREATING`, `ACTIVE`, `DELETING`, `FAILED`.
* `status` - The status of the EKS cluster. One of `CREATING`, `ACTIVE`, `DELETING`, `FAILED`.
* `version` - The Kubernetes server version for the cluster.
* `vpc_config` - Additional nested attributes:
* `cluster_security_group_id` - The cluster security group that was created by Amazon EKS for the cluster.
Expand Down