You should use MORPH_ELLIPSE, with the same size for both axis. Check the OpenCV tutorials.
This will produce a ball / circle shaped element with a diameter of 5:
cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(5,5))
You could use skimage.morphology.ball.
From the doc:
skimage.morphology.ball(radius, dtype=<class 'numpy.uint8'>)
Generates a ball-shaped footprint.
This is the 3D equivalent of a disk. A pixel is within the neighborhood if the Euclidean distance between it and the origin is no greater than radius.
This answer explains how to use MORPH_CLOSE around the edge of an image by adding a buffer to the image.
You can add a buffer by creating an image of zeros using numpy:
# Import packages
import cv2
import numpy as np
import matplotlib.pyplot as plt
# Read in the image
img = cv2.imread('/home/stephen/Desktop/OKoUh.png', 0)
# Create a black bufffer around the image
h,w = img.shape
buffer = max(h,w)
bg = np.zeros((h+buffer*2, w+buffer*2), np.uint8)
bg[buffer:buffer+h, buffer:buffer+w] = img
Then you can iterate and check how it looks at different kernel sizes:
for close_size in range(1,11):
temp = bg.copy()
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (close_size, close_size))
result = cv2.morphologyEx(temp, cv2.MORPH_CLOSE, kernel)
results = result[buffer:buffer+h, buffer:buffer+w]
cv2.imshow('img', result)
cv2.waitKey()
My results:

Based on Stephen's answer, here is the snippet I ended up implementing :
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (close_size, close_size))
# Padding
im=cv2.copyMakeBorder(im,close_size,close_size,close_size,close_size,
borderType=cv2.BORDER_CONSTANT, value = 0)
# Closing
im = cv2.morphologyEx(im, cv2.MORPH_CLOSE, kernel)
# Unpadding
im = im[close_size:-close_size,close_size:-close_size]
As I mentionned in a comment, this can lead to a much longer computation time for large kernel sizes. It's possible that padding with lower values, eg close_size/2 would be enough to prevent border issues (didn't test it).

