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

feat(backend): add postgres initialization #9798

Merged
merged 24 commits into from
Aug 10, 2023

Conversation

Linchin
Copy link
Contributor

@Linchin Linchin commented Jul 28, 2023

Description of your changes:
Part 1 of supporting PostgreSQL:

  • Adds driver for PostgreSQL
  • Supports initializing PostgreSQL
  • Adds integration tests for MySQL or PostgreSQL initialization
  • Moves client_manager.go to its own package and folder for ease of testing
  • Allows users to select which type of database they want to run integration test against
  • Fixes a typo in the PostgreSQL deployment guide
  • Makes PostgreSQL no longer load balancer

Checklist:

@google-oss-prow google-oss-prow bot added size/XL and removed size/L labels Aug 2, 2023
go-licenses save ./backend/src/agent/persistence --save_path /tmp/NOTICES

# RUN go-licenses csv ./backend/src/agent/persistence > /tmp/licenses.csv && \
# diff /tmp/licenses.csv backend/third_party_licenses/persistence_agent.csv && \
# go-licenses save ./backend/src/agent/persistence --save_path /tmp/NOTICES
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we save license file to /tmp/NOTICES twice? What is the reason for this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for pointing this out. I was temporarily disabling them, because the generated license files apparently changed although I didn't change this part of code. I intend to double check if we need to update all the license files.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should have this sorted out before we can merge the PR.

backend/src/apiserver/client/sql.go Show resolved Hide resolved
@@ -44,3 +45,26 @@ func CreateMySQLConfig(user, password string, mysqlServiceHost string,
AllowNativePasswords: true,
}
}

func CreatePostgreSQLConfig(user, password, postgresHost, dbName string, postgresPort uint16,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you plan to introduce sslmode for security support in the coming PRs? It is important to make sure we are using secure channel when reading/writing to DB.

Reference: https://github.com/google/ml-metadata/blob/master/ml_metadata/proto/metadata_store.proto#L714-L743

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be great to improve security, but I wonder if it is necessary, since the DB and the backend are hosted in the same cluster.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This allows scenario like connecting to a managed database service. We can discuss about this in future implementation.

Comment on lines +58 to +60
if password != "" {
fmt.Fprintf(&b, "password=%s ", password)
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

passfile sounds like a great way to enhance security. Could you tell me more about it? My guess is this will allow users to pass a file containing the password as part of the deployment config. Maybe this can be added together with other configs users can set.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is right! So the passfile identify the location path to a password, so you don't need to expose the secret via deployment. https://www.postgresql.org/docs/current/libpq-pgpass.html When passing this parameter to postgresql, the password field can be omitted.

We can add this configuration in the coming PR, if this makes sense to you.

Comment on lines +61 to +63
if postgresHost != "" {
fmt.Fprintf(&b, "host=%s ", postgresHost)
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this is necessary, since the host address here is simply for communications inside the cluster. Could you tell me more why this is beneficial? Thank you!

Copy link
Collaborator

@zijianjoy zijianjoy Aug 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure thing, hostaddr is helpful when we are connecting to external database, for example: CloudSQL. Yes we can use host which is handy for Unix-domain communication, but hostaddr allows postgresql client to make TCP/IP communication instead.

Again, we can implement this feature in future PR.

func InitDBClient(initConnectionTimeout time.Duration) *storage.DB {
// Allowed driverName values:
// 1) To use MySQL, use `mysql`
// 2) To use PostgreSQL, use `pgx`
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we choose pgx for postgresql in DBDriverName flag? Is there an alternative which is easier for people to understand?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1, pgx feels weird, albeit I saw that's the name of the go package you're using. I think postgres would be still better here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used this because we are using the package pgx as the PostgreSQL driver, and it is needed in the sql.Open() function to create the correct DB instance. See the example here. I agree it may be quite confusing to people, but I was weighting this against being consistent with the driver name we are using, so I eventually opted for making it clear in the comments to reduce the confusion. WDYT?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay I think @Linchin 's point is reasonable, we just need to make sure we document this parameter is being used as a sql driver flag: https://github.com/golang/go/wiki/SQLDrivers.

Comment on lines 265 to 268
case "mysql":
textFormat = "longtext not null"
case "pgx":
textFormat = "text"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might need to define some const for value like text and longtext not null.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you help me understand why it is better to use const values here? Thank you.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DB dialects should be stored in a centralized place, so it helps reviewing, modifying and extending. For example: If we integrate with a new DB type in the future, I hope that I can view the existing syntax differences easily in one place, instead of trying to catch those adhoc variables during implementation.

Therefore, my recommendation is to define a const for MYSQL_TEXT_FORMAT and PGX_TEXT_FORMAT in a centralized file: https://github.com/kubeflow/pipelines/blob/master/backend/src/apiserver/storage/db.go

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you so much for the thourought explanation! I added the constants in backend/src/apiserver/client/sql.go instead, because this is a SQL related dependency that client_manager is already using.

backend/src/apiserver/client_manager/client_manager.go Outdated Show resolved Hide resolved
Comment on lines 395 to 396
common.GetStringConfigWithDefault(postgresUser, ""),
common.GetStringConfigWithDefault(postgresPassword, ""),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to set a default user like mysql does?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, thanks for finding this! I should add a default user here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added

AddForeignKey("RunUUID", "run_details(UUID)", "CASCADE" /* onDelete */, "CASCADE" /* update */)
if response.Error != nil {
glog.Fatalf("Failed to create a foreign key for RunUUID in task table. Error: %s", response.Error)
switch driverName {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think extracting the different behavior of each driver to their own module is helpful. But feel free to defer it to future PRs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. This part was more of a patch work to make it work for now.

go-licenses save ./backend/src/agent/persistence --save_path /tmp/NOTICES

# RUN go-licenses csv ./backend/src/agent/persistence > /tmp/licenses.csv && \
# diff /tmp/licenses.csv backend/third_party_licenses/persistence_agent.csv && \
# go-licenses save ./backend/src/agent/persistence --save_path /tmp/NOTICES
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should have this sorted out before we can merge the PR.

postgresPort = "DBConfig.PostgreSQLConfig.Port"
postgresUser = "DBConfig.PostgreSQLConfig.User"
postgresPassword = "DBConfig.PostgreSQLConfig.Password"
postgresDBName = "DBConfig.PostgreSQLConfig.DBName"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The newly added Postgres configs seems so generic and completely covered by existing configs for mysql already. Do we need to separate them?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that they are pretty much the same fields here, but I think it is possible that there can be different values for the two types of DB, as well as different configs available, so it's safer to have one set of values for each of them. For example, the default host name and port number for MySQL and PostgreSQL are different.

@@ -12,15 +12,17 @@
// See the License for the specific language governing permissions and
// limitations under the License.

package main
package clientmanager
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reason for moving this file?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point! I moved it because I added integration test for functions here, but it's impossible to test stuff in "main" from a different package in Golang. Reference: https://stackoverflow.com/questions/53997114/how-to-test-the-main-package-in-golang-from-a-test-package

func InitDBClient(initConnectionTimeout time.Duration) *storage.DB {
// Allowed driverName values:
// 1) To use MySQL, use `mysql`
// 2) To use PostgreSQL, use `pgx`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1, pgx feels weird, albeit I saw that's the name of the go package you're using. I think postgres would be still better here.

// Returns the same error, if it's not "already exists" related.
// Otherwise, return nil.
func ignoreAlreadyExistError(err error) error {
if err != nil && strings.Contains(err.Error(), "exists") {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method seems very fragile. I imagine there's could be multiple scenarios matching keyword "exists".
By the way, why do we need to ignore this now? Was it an issue for MySQL in the past or does only applies to Postgres?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this because MySQL simply ignores if the foreign key already exist, but PostgreSQL reports an error. A more robust way I can think of is to use an if/else branch for the two SQLs and handle them separately.

backend/src/apiserver/client_manager/client_manager.go Outdated Show resolved Hide resolved

### Run database tests with PostgreSQL

To run this test, you need to first deploy the PostgreSQL images on your Kubernetes cluster. For how to deploy,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it in your next step to automate the test? We can rely on manual test.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally we should, and also include PostgreSQL set of tests in the test infra.

Comment on lines 597 to 600
if driverName == "pgx" && err != nil && strings.Contains(err.Error(), "already exists") {
return nil
}
if driverName == "mysql" && err != nil && strings.Contains(err.Error(), "database exists") {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can define consts for each error message keyword already exists and database exists in a centralized place: https://github.com/kubeflow/pipelines/blob/master/backend/src/apiserver/storage/db.go

@@ -2,7 +2,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- pg-deployment.yaml
- pg-pv.yaml
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I shouldn't need a PV resource, it is automatically created by PVC when it is bound to a deployment resource.

Copy link
Contributor Author

@Linchin Linchin Aug 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about this. I added the PV file because the PVC had trouble binding due to lack of PV. Maybe I was doing something wrong during my deployment. Could you give me more reference on this? Thank you.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can look at the Events for the PVC to learn more about why is PV not created. Maybe the disk driver has something wrong when provisioning a PV. But we shouldn't create PV resource manually in codebase. Refer to MySQL manifest folder: https://github.com/kubeflow/pipelines/tree/master/manifests/kustomize/third-party/mysql/base

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right about this. I tried a fresh deployment without the PV, and it passed the tests. The previous error was likely caused by something else. I removed the pv declaration.

@Linchin
Copy link
Contributor Author

Linchin commented Aug 9, 2023

/test kubeflow-pipeline-e2e-test
/test kubeflow-pipeline-upgrade-test

@Linchin
Copy link
Contributor Author

Linchin commented Aug 9, 2023

part of #9813

@Linchin
Copy link
Contributor Author

Linchin commented Aug 9, 2023

/test kubeflow-pipeline-e2e-test
/test kubeflow-pipeline-upgrade-test

Copy link
Collaborator

@zijianjoy zijianjoy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/lgtm

Great work on this PR, Lingqing!
Waiting for Chen for a final approval.

@google-oss-prow google-oss-prow bot added the lgtm label Aug 10, 2023
Copy link
Member

@chensun chensun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/lgtm

Thanks!

Copy link
Member

@chensun chensun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/approve

Thanks!

@google-oss-prow
Copy link

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: chensun

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@google-oss-prow google-oss-prow bot merged commit e1f0c01 into kubeflow:master Aug 10, 2023
@iciclespider
Copy link

I get the following error when the db driver name is configured to "gpx":

client_manager.go:428] sql: unknown driver "pgx" (forgotten import?)

According to this, it appears to be a missing import which will register the needed driver: https://stackoverflow.com/a/52791919

stijntratsaertit pushed a commit to stijntratsaertit/kfp that referenced this pull request Feb 16, 2024
* add postgres initialization

* remove load balancer

* go mod tidy

* update license

* license update for viewer

* (test) disable controller license check

* (test) disable persistence agence licence check

* (test) disable scheduled workflow license check

* (test) disable cacheserver license check

* fix db config location

* fix mysql support

* test

* test

* no long set host address

* address comments

* address comments and enable license check

* format

* remove extra blank line

* update licenses

* cache server license

* address comments

* centralize error message

* remove pv in postgres deployment
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants