Multi-user, auth-enabled Kubeflow with kfctl_existing_arrikto

Instructions for installing Kubeflow with kfctl_existing_arrikto.yaml config

Follow these instructions if you want to install Kubeflow on an existing Kubernetes cluster.

This installation of Kubeflow is maintained byArrikto, it is geared towards existing Kubernetesclusters and does not depend on any cloud-specific feature.

In this reference architecture, we use Dex andIstio for vendor-neutral authentication.

This deployment works well for on-prem installations, where companies/organizations need LDAP/AD integration for multi-user authentication, and they don’t want to depend on any cloud-specific feature.

kfctl_existing_arrikto_architecture

Read the relevant article for more info about this architecture.

Prerequisites

You need a Kubernetes Cluster with LoadBalancer support.

If you don’t have support for LoadBalancer on your cluster, please follow the instructions below to deploy MetalLB in Layer 2 mode. (You can read more about Layer 2 mode in the MetalLB docs.)

MetalLB deployment

Deploy MetalLB:

  • Apply the manifest:
  1. kubectl apply -f https://raw.githubusercontent.com/google/metallb/v0.8.1/manifests/metallb.yaml
  • Allocate a pool of addresses on your local network for MetalLB to use. Youneed at least one address for the Istio Gateway. This example assumesaddresses 10.0.0.100-10.0.0.110. You must modify these addresses based onyour environment.
  1. cat <<EOF | kubectl apply -f -
  2. apiVersion: v1
  3. kind: ConfigMap
  4. metadata:
  5. namespace: metallb-system
  6. name: config
  7. data:
  8. config: |
  9. address-pools:
  10. - name: default
  11. protocol: layer2
  12. addresses:
  13. - 10.0.0.100-10.0.0.110
  14. EOF

Ensure that MetalLB works as expected (optional):

  • Create a dummy service:
  1. kubectl create service loadbalancer nginx --tcp=80:80
  2. service/nginx created
  • Ensure that MetalLB has allocated an IP address for the service:
  1. kubectl describe service nginx
  2. ...
  3. Events:
  4. Type Reason Age From Message
  5. ---- ------ ---- ---- -------
  6. Normal IPAllocated 69s metallb-controller Assigned IP "10.0.0.101"
  • Check the corresponding MetalLB logs:
  1. kubectl logs -n metallb-system -l component=controller
  2. ...
  3. {"caller":"service.go:98","event":"ipAllocated","ip":"10.0.0.101","msg":"IP address assigned by controller","service":"default/nginx","ts":"2019-08-09T15:12:09.376779263Z"}
  • Create a pod that will be exposed with the service:
  1. kubectl run nginx --image nginx --restart=Never -l app=nginx
  2. pod/nginx created
  • Ensure that MetalLB has assigned a node to announce the allocated IP address:
  1. kubectl describe service nginx
  2. ...
  3. Events:
  4. Type Reason Age From Message
  5. ---- ------ ---- ---- -------
  6. Normal nodeAssigned 4s metallb-speaker announcing from node "node-2"
  • Check the corresponding MetalLB logs:
  1. kubectl logs -n metallb-system -l component=speaker
  2. ...
  3. {"caller":"main.go:246","event":"serviceAnnounced","ip":"10.0.0.101","msg":"service has IP, announcing","pool":"default","protocol":"layer2","service":"default/nginx","ts":"2019-08-09T15:14:02.433876894Z"}
  • Check that MetalLB responds to ARP requests for the allocated IP address:
  1. arping -I eth0 10.0.0.101
  2. ...
  3. ARPING 10.0.0.101 from 10.0.0.204 eth0
  4. Unicast reply from 10.0.0.101 [6A:13:5A:D2:65:CB] 2.619ms
  • Check the corresponding MetalLB logs:
  1. kubectl logs -n metallb-system -l component=speaker
  2. ...
  3. {"caller":"arp.go:102","interface":"eth0,"ip":"10.0.0.101","msg":"got ARP request for service IP, sending response","responseMAC":"6a:13:5a:d2:65:cb","senderIP":"10.0.0.204","senderMAC":"9a:1f:7c:95:ca:dc","ts":"2019-08-09T15:14:52.912056021Z"}
  • Verify that everything works as expected:
  1. curl http://10.0.0.101
  2. ...
  3. <p><em>Thank you for using nginx.</em></p>
  4. ...
  • Clean up:
  1. kubectl delete service nginx
  2. kubectl delete pod nginx

Deploy Kubeflow

Follow these steps to deploy Kubeflow:

  1. tar -xvf kfctl_<release tag>_<platform>.tar.gz
  • Run the following commands to set up and deploy Kubeflow. The code below includes an optional command to add the binary kfctl to your path. If you don’t add the binary to your path, you must use the full path to the kfctl binary each time you run it.
  1. # Add kfctl to PATH, to make the kfctl binary easier to use.
  2. export PATH=$PATH:"<path to kfctl>"
  3. export KFAPP="<your choice of application directory name>"
  4. export CONFIG="https://raw.githubusercontent.com/kubeflow/kubeflow/v0.6-branch/bootstrap/config/kfctl_existing_arrikto.0.6.2.yaml"
  5. # Specify credentials for the default user.
  6. export KUBEFLOW_USER_EMAIL="admin@kubeflow.org"
  7. export KUBEFLOW_PASSWORD="12341234"
  8. kfctl init ${KFAPP} --config=${CONFIG} -V
  9. cd ${KFAPP}
  10. kfctl generate all -V
  11. kfctl apply all -V
  • ${KFAPP} - the name of a directory where you want Kubeflowconfigurations to be stored. This directory is created when you runkfctl init. If you want a custom deployment name, specify that name here.The value of this variable becomes the name of your deployment.The value of this variable cannot be greater than 25 characters. It mustcontain just the directory name, not the full path to the directory.The content of this directory is described in the next section.

Accessing Kubeflow

Log in as a static user

After deploying Kubeflow, the Kubeflow dashboard is available at the Istio Gateway IP.To get the Istio Gateway IP, run:

  1. kubectl get svc -n istio-system istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}'

Get the IP and open it in a browser: https://<LoadBalancerIP address>/.

Enter the credentials you specified in KUBEFLOW_USER_EMAIL, KUBEFLOW_PASSWORD and access the Kubeflow dashboard!

Add static users for basic auth

To add users to basic auth, you just have to edit the Dex ConfigMap under the key staticPasswords.

  1. # Download the dex config
  2. kubectl get configmap dex -n kubeflow -o jsonpath='{.data.config\.yaml}' > dex-config.yaml
  3. # Edit the dex config with extra users.
  4. # The password must be hashed with bcrypt with an at least 10 difficulty level.
  5. # You can use an online tool like: https://passwordhashing.com/BCrypt
  6. # After editing the config, update the ConfigMap
  7. kubectl create configmap dex --from-file=config.yaml=dex-config.yaml -n kubeflow --dry-run -oyaml | kubectl apply -f -

Log in with LDAP / Active Directory

As you saw in the overview, we use Dex for providing user authentication.Dex supports several authentication methods:

  • Static users, as described above
  • LDAP / Active Directory
  • External Identity Provider (IdP) (for example Google, LinkedIn, GitHub, …)

This section focuses on setting up Dex to authenticate with an existing LDAP database.

  • (Optional) If you don’t have an LDAP database, you can set one up following these instructions:

    • Deploy a new LDAP Server as a StatefulSet. This also deploys phpLDAPadmin, a GUI for interacting with your LDAP Server.

LDAP Server Manifest

  1. apiVersion: v1
  2. kind: Service
  3. metadata:
  4. labels:
  5. app: ldap
  6. name: ldap-service
  7. namespace: kubeflow
  8. spec:
  9. type: ClusterIP
  10. clusterIP: None
  11. ports:
  12. - port: 389
  13. selector:
  14. app: ldap
  15. ---
  16. apiVersion: apps/v1
  17. kind: StatefulSet
  18. metadata:
  19. name: ldap
  20. namespace: kubeflow
  21. labels:
  22. app: ldap
  23. spec:
  24. serviceName: ldap-service
  25. replicas: 1
  26. selector:
  27. matchLabels:
  28. app: ldap
  29. template:
  30. metadata:
  31. labels:
  32. app: ldap
  33. spec:
  34. containers:
  35. - name: ldap
  36. image: osixia/openldap:1.2.4
  37. volumeMounts:
  38. - name: ldap-data
  39. mountPath: /var/lib/ldap
  40. - name: ldap-config
  41. mountPath: /etc/ldap/slapd.d
  42. ports:
  43. - containerPort: 389
  44. name: openldap
  45. env:
  46. - name: LDAP_LOG_LEVEL
  47. value: "256"
  48. - name: LDAP_ORGANISATION
  49. value: "Example"
  50. - name: LDAP_DOMAIN
  51. value: "example.com"
  52. - name: LDAP_ADMIN_PASSWORD
  53. value: "admin"
  54. - name: LDAP_CONFIG_PASSWORD
  55. value: "config"
  56. - name: LDAP_BACKEND
  57. value: "mdb"
  58. - name: LDAP_TLS
  59. value: "false"
  60. - name: LDAP_REPLICATION
  61. value: "false"
  62. - name: KEEP_EXISTING_CONFIG
  63. value: "false"
  64. - name: LDAP_REMOVE_CONFIG_AFTER_SETUP
  65. value: "true"
  66. volumes:
  67. - name: ldap-config
  68. emptyDir: {}
  69. volumeClaimTemplates:
  70. - metadata:
  71. name: ldap-data
  72. spec:
  73. accessModes: [ "ReadWriteOnce" ]
  74. resources:
  75. requests:
  76. storage: 10Gi
  77. ---
  78. apiVersion: v1
  79. kind: Service
  80. metadata:
  81. labels:
  82. app: phpldapadmin
  83. name: phpldapadmin-service
  84. namespace: kubeflow
  85. spec:
  86. type: ClusterIP
  87. ports:
  88. - port: 80
  89. selector:
  90. app: phpldapadmin
  91. ---
  92. apiVersion: apps/v1
  93. kind: Deployment
  94. metadata:
  95. name: phpldapadmin
  96. namespace: kubeflow
  97. labels:
  98. app: phpldapadmin
  99. spec:
  100. replicas: 1
  101. selector:
  102. matchLabels:
  103. app: phpldapadmin
  104. template:
  105. metadata:
  106. labels:
  107. app: phpldapadmin
  108. spec:
  109. containers:
  110. - name: phpldapadmin
  111. image: osixia/phpldapadmin:0.8.0
  112. ports:
  113. - name: http-server
  114. containerPort: 80
  115. env:
  116. - name: PHPLDAPADMIN_HTTPS
  117. value: "false"
  118. - name: PHPLDAPADMIN_LDAP_HOSTS
  119. value : "#PYTHON2BASH:[{'ldap-service.kubeflow.svc.cluster.local': [{'server': [{'tls': False}]},{'login': [ {'bind_id': 'cn=admin,dc=example,dc=com'}]}]}]"
  • Seed the LDAP database with new entries.
  1. kubectl exec -it -n kubeflow ldap-0 -- bash
  2. ldapadd -x -D "cn=admin,dc=example,dc=com" -W
  3. # Enter password "admin".
  4. # Press Ctrl+D to complete after pasting the snippet below.

LDAP Seed Users and Groups

  1. # If you used the OpenLDAP Server deployment in step 1,
  2. # then this object already exists.
  3. # If it doesn't, uncomment this.
  4. #dn: dc=example,dc=com
  5. #objectClass: dcObject
  6. #objectClass: organization
  7. #o: Example
  8. #dc: example
  9. dn: ou=People,dc=example,dc=com
  10. objectClass: organizationalUnit
  11. ou: People
  12. dn: cn=Nick Kiliadis,ou=People,dc=example,dc=com
  13. objectClass: person
  14. objectClass: inetOrgPerson
  15. givenName: Nick
  16. sn: Kiliadis
  17. cn: Nick Kiliadis
  18. uid: nkili
  19. mail: nkili@example.com
  20. userpassword: 12341234
  21. dn: cn=Robin Spanakopita,ou=People,dc=example,dc=com
  22. objectClass: person
  23. objectClass: inetOrgPerson
  24. givenName: Robin
  25. sn: Spanakopita
  26. cn: Robin Spanakopita
  27. uid: rspanakopita
  28. mail: rspanakopita@example.com
  29. userpassword: 43214321
  30. # Group definitions.
  31. dn: ou=Groups,dc=example,dc=com
  32. objectClass: organizationalUnit
  33. ou: Groups
  34. dn: cn=admins,ou=Groups,dc=example,dc=com
  35. objectClass: groupOfNames
  36. cn: admins
  37. member: cn=Nick Kiliadis,ou=People,dc=example,dc=com
  38. dn: cn=developers,ou=Groups,dc=example,dc=com
  39. objectClass: groupOfNames
  40. cn: developers
  41. member: cn=Nick Kiliadis,ou=People,dc=example,dc=com
  42. member: cn=Robin Spanakopita,ou=People,dc=example,dc=com
  • To use your LDAP/AD server with Dex, you have to edit the Dex config. To edit the ConfigMap containing the Dex config, follow these steps:

    • Get the current Dex config from the corresponding Config Map.
  1. kubectl get configmap dex -n kubeflow -o jsonpath='{.data.config\.yaml}' > dex-config.yaml
  • Add the LDAP-specific options. Here is an example to help you out. It is configured to work with the example LDAP Server you set up previously.

Dex LDAP Config Section

  1. connectors:
  2. - type: ldap
  3. # Required field for connector id.
  4. id: ldap
  5. # Required field for connector name.
  6. name: LDAP
  7. config:
  8. # Host and optional port of the LDAP server in the form "host:port".
  9. # If the port is not supplied, it will be guessed based on "insecureNoSSL",
  10. # and "startTLS" flags. 389 for insecure or StartTLS connections, 636
  11. # otherwise.
  12. host: ldap-service.kubeflow.svc.cluster.local:389
  13. # Following field is required if the LDAP host is not using TLS (port 389).
  14. # Because this option inherently leaks passwords to anyone on the same network
  15. # as dex, THIS OPTION MAY BE REMOVED WITHOUT WARNING IN A FUTURE RELEASE.
  16. #
  17. insecureNoSSL: true
  18. # If a custom certificate isn't provide, this option can be used to turn off
  19. # TLS certificate checks. As noted, it is insecure and shouldn't be used outside
  20. # of explorative phases.
  21. #
  22. insecureSkipVerify: true
  23. # When connecting to the server, connect using the ldap:// protocol then issue
  24. # a StartTLS command. If unspecified, connections will use the ldaps:// protocol
  25. #
  26. startTLS: false
  27. # Path to a trusted root certificate file. Default: use the host's root CA.
  28. # rootCA: /etc/dex/ldap.ca
  29. # clientCert: /etc/dex/ldap.cert
  30. # clientKey: /etc/dex/ldap.key
  31. # A raw certificate file can also be provided inline.
  32. # rootCAData: ( base64 encoded PEM file )
  33. # The DN and password for an application service account. The connector uses
  34. # these credentials to search for users and groups. Not required if the LDAP
  35. # server provides access for anonymous auth.
  36. # Please note that if the bind password contains a `$`, it has to be saved in an
  37. # environment variable which should be given as the value to `bindPW`.
  38. bindDN: cn=admin,dc=example,dc=com
  39. bindPW: admin
  40. # The attribute to display in the provided password prompt. If unset, will
  41. # display "Username"
  42. usernamePrompt: username
  43. # User search maps a username and password entered by a user to a LDAP entry.
  44. userSearch:
  45. # BaseDN to start the search from. It will translate to the query
  46. # "(&(objectClass=person)(uid=<username>))".
  47. baseDN: ou=People,dc=example,dc=com
  48. # Optional filter to apply when searching the directory.
  49. filter: "(objectClass=inetOrgPerson)"
  50. # username attribute used for comparing user entries. This will be translated
  51. # and combined with the other filter as "(<attr>=<username>)".
  52. username: uid
  53. # The following three fields are direct mappings of attributes on the user entry.
  54. # String representation of the user.
  55. idAttr: uid
  56. # Required. Attribute to map to Email.
  57. emailAttr: mail
  58. # Maps to display name of users. No default value.
  59. nameAttr: givenName
  60. # Group search queries for groups given a user entry.
  61. groupSearch:
  62. # BaseDN to start the search from. It will translate to the query
  63. # "(&(objectClass=group)(member=<user uid>))".
  64. baseDN: ou=Groups,dc=example,dc=com
  65. # Optional filter to apply when searching the directory.
  66. filter: "(objectClass=groupOfNames)"
  67. # Following two fields are used to match a user to a group. It adds an additional
  68. # requirement to the filter that an attribute in the group must match the user's
  69. # attribute value.
  70. userAttr: DN
  71. groupAttr: member
  72. # Represents group name.
  73. nameAttr: cn
  • Append the LDAP config section to the dex config.
  1. cat dex-config.yaml dex-config-ldap-partial.yaml > dex-config-final.yaml
  • Apply the new config.
  1. kubectl create configmap dex --from-file=config.yaml=dex-config-final.yaml -n kubeflow --dry-run -oyaml | kubectl apply -f -
  • Restart the Dex deployment, by doing one of the following:

    • Force recreation, by deleting the Dex deployment’s Pod(s).
      • kubectl delete pods -n kubeflow -l app=dex
    • Trigger a rolling update, by adding/updating a label on the PodTemplate of the Dex deployment.
      • kubectl edit deployment dex -n kubeflow will open the Dex deployment in a text editor.
      • Add or update a label on the PodTemplate.
      • Save the deployment to trigger a rolling update.

Troubleshooting

If the Kubeflow dashboard is not available at https://<LoadBalancerIP address> ensure that:

  • the LoadBalancer service for Istio has obtained an external IP, for example:
  1. kubectl get services -n istio-system istio-ingressgateway -o yaml
  2. ...
  3. status:
  4. loadBalancer:
  5. ingress:
  6. - ip: 10.0.0.100

If not, then probably there is a misconfiguration of MetalLB.

  • the virtual services have been created:
  1. kubectl get virtualservices -n kubeflow
  2. kubectl get virtualservices -n kubeflow centraldashboard -o yaml

If not, then kfctl has aborted for some reason, and not completed successfully.

  • OIDC auth service redirects you to Dex:
  1. curl -k https://<LoadBalancerIP address>/ -v
  2. ...
  3. < HTTP/2 302
  4. < content-type: text/html; charset=utf-8
  5. < location:
  6. /dex/auth?client_id=kubeflow-authservice-oidc&redirect_uri=%2Flogin%2Foidc&response_type=code&scope=openid+profile+email+groups&state=vSCMnJ2D
  7. < date: Fri, 09 Aug 2019 14:33:21 GMT
  8. < content-length: 181
  9. < x-envoy-upstream-service-time: 0
  10. < server: istio-envoy

Please join the Kubeflow Slack to report any issues, request help, and give us feedback on this config.

Some additional debugging information:

OIDC Service logs:

  1. kubectl logs -n istio-system -l app=authservice

Dex logs:

  1. kubectl logs -n kubeflow -l app=dex

Istio ingress-gateway logs:

  1. kubectl logs -n istio-system -l istio=ingressgateway

Istio ingressgateway service:

  1. kubectl get service -n istio-system istio-ingressgateway -o yaml

MetalLB logs:

  1. kubectl logs -n metallb-system -l component=speaker
  2. ...
  3. {"caller":"arp.go:102","interface":"br100","ip":"10.0.0.100","msg":"got ARP request for service IP, sending response","responseMAC":"62:41:bd:5f:cc:0d","senderIP":"10.0.0.204","senderMAC":"9a:1f:7c:95:ca:dc","ts":"2019-07-31T13:19:19.7082836Z"}
  1. kubectl logs -n metallb-system -l component=controller
  2. ...
  3. {"caller":"service.go:98","event":"ipAllocated","ip":"10.0.0.100","msg":"IP address assigned by controller","service":"istio-system/istio-ingressgateway","ts":"2019-07-31T12:17:46.234638607Z"}

Delete Kubeflow

Run the following commands to delete your deployment and reclaim all resources:

  1. cd ${KFAPP}
  2. # If you want to delete all the resources, run:
  3. kfctl delete all

Understanding the deployment process

The deployment process is controlled by 4 different commands:

  • init - one time set up.
  • generate - creates config files defining the various resources.
  • apply - creates or updates the resources.
  • delete - deletes the resources.

With the exception of init, all commands take an argument which describes theset of resources to apply the command to; this argument can be one of thefollowing:

  • k8s - all resources that run on Kubernetes.
  • all - platform and Kubernetes resources.

App layout

Your Kubeflow app directory contains the following files and directories:

  • ${KFAPP}/app.yaml defines configurations related to your Kubeflow deployment.
  • ${KFAPP}/kustomize: contains the YAML manifests that will be deployed.

Next steps