Merge pull request #6184 from cybozu-go/ceph-raw-mode-on-lv-backed-pvc

ceph: raw mode OSD on LV-backed PVC
This commit is contained in:
Sébastien Han authored and GitHub committed 2020-10-15 09:46:56 +02:00
commit f0b2c6eacf
4 files changed
+316 -24

No files matched your search

+4 -2
View File
@@ -815,9 +815,11 @@ If you still decide to configure an OSD on LVM, please keep the following in min
- Disable `lvmetad.`
- Avoid configuration of LVs from the host. In addition, don't touch the VGs and physical volumes that back these LVs.
- Avoid incrementing the `count` field of `storageClassDeviceSets` and create a new LV that backs a OSD simultaneously.
- Avoid incrementing the `count` field of `storageClassDeviceSets` and create a new LV that backs an OSD simultaneously.
You can know whether the above-mentioned tag exists tag with the command: `sudo lvs -o lv_name,lv_tags`. If the `lv_tag` field is empty in an LV corresponding to the OSD lv_tags, this OSD encountered the problem. In this case, please [retire this OSD](ceph-osd-mgmt.md#remove-an-osd) or replace with other new OSD before restarting.
You can know whether the above-mentioned tag exists with the command: `sudo lvs -o lv_name,lv_tags`. If the `lv_tag` field is empty in an LV corresponding to the OSD lv_tags, this OSD encountered the problem. In this case, please [retire this OSD](ceph-osd-mgmt.md#remove-an-osd) or replace with other new OSD before restarting.
This problem doesn't happen in newly created LV-backed PVCs because OSD container doesn't modify LVM metadata anymore. The existing lvm mode OSDs work continuously even thought upgrade your Rook. However, using the raw mode OSDs is recommended because of the above-mentioned problem. You can replace the existing OSDs with raw mode OSDs by retiring them and adding new OSDs one by one. See the documents [Remove an OSD](ceph-osd-mgmt.md#remove-an-osd) and [Add an OSD on a PVC](ceph-osd-mgmt.md#add-an-osd-on-a-pvc).
## OSD prepare job fails due to low aio-max-nr setting
+4
View File
@@ -532,3 +532,7 @@ k8s.gcr.io/sig-storage/k8scsi/csi-resizer:v1.0.0
k8s.gcr.io/sig-storage/csi-snapshotter:v3.0.0
k8s.gcr.io/sig-storage/csi-resizer:v1.0.0
```
## Replace lvm mode OSDs with raw mode (if you use LV-backed PVC)
For LV-backed PVC, we recommend replacing lvm mode OSDs with raw mode OSDs. See [common issue](Documentation/ceph-common-issues.md#lvm-metadata-can-be-corrupted-with-osd-on-lv-backed-pvc).
+22 -22
View File
@@ -110,6 +110,10 @@ func (a *OsdAgent) configureCVDevices(context *clusterd.Context, devices *Device
// For LV mode
lvPath = getDeviceLVPath(context, fmt.Sprintf("/mnt/%s", a.nodeName))
lvBackedPV, err := sys.IsLV(fmt.Sprintf("/mnt/%s", a.nodeName), context.Executor)
if err != nil {
return nil, errors.Wrap(err, "failed to check device type")
}
// List THE existing OSD configured with ceph-volume lvm mode
lvmOsds, err = GetCephVolumeLVMOSDs(context, a.clusterInfo, a.clusterInfo.FSID, lvPath, skipLVRelease, lvBackedPV)
@@ -117,9 +121,13 @@ func (a *OsdAgent) configureCVDevices(context *clusterd.Context, devices *Device
logger.Infof("failed to get device already provisioned by ceph-volume lvm. %v", err)
}
osds = append(osds, lvmOsds...)
if len(osds) > 0 {
// "ceph-volume raw list" lists the existing OSD even if it is configured with lvm mode, so escape here to avoid dupe.
return osds, nil
}
// List THE existing OSD configured with ceph-volume raw mode
if a.clusterInfo.CephVersion.IsAtLeast(cephVolumeRawModeMinCephVersion) && !lvBackedPV {
if a.clusterInfo.CephVersion.IsAtLeast(cephVolumeRawModeMinCephVersion) {
// For block mode
block = fmt.Sprintf("/mnt/%s", a.nodeName)
@@ -143,7 +151,7 @@ func (a *OsdAgent) configureCVDevices(context *clusterd.Context, devices *Device
}
// List existing OSD(s) configured with ceph-volume lvm mode
lvmOsds, err = GetCephVolumeLVMOSDs(context, a.clusterInfo, a.clusterInfo.FSID, lvPath, false, lvBackedPV)
lvmOsds, err = GetCephVolumeLVMOSDs(context, a.clusterInfo, a.clusterInfo.FSID, lvPath, false, false)
if err != nil {
logger.Infof("failed to get devices already provisioned by ceph-volume. %v", err)
}
@@ -172,9 +180,7 @@ func (a *OsdAgent) configureCVDevices(context *clusterd.Context, devices *Device
// Update LVM configuration file
// Only do this after Ceph Nautilus 14.2.6 since it will use the ceph-volume raw mode by default and not LVM anymore
//
// Or keep doing this if the PV is backend by an LV already
if !a.clusterInfo.CephVersion.IsAtLeast(cephVolumeRawModeMinCephVersion) || lvBackedPV {
if !a.clusterInfo.CephVersion.IsAtLeast(cephVolumeRawModeMinCephVersion) {
if err := UpdateLVMConfig(context, a.pvcBacked, lvBackedPV); err != nil {
return nil, errors.Wrap(err, "failed to update lvm configuration file")
}
@@ -199,7 +205,7 @@ func (a *OsdAgent) configureCVDevices(context *clusterd.Context, devices *Device
osds = append(osds, lvmOsds...)
// List THE configured OSD with ceph-volume raw mode
if a.clusterInfo.CephVersion.IsAtLeast(cephVolumeRawModeMinCephVersion) && !lvBackedPV {
if a.clusterInfo.CephVersion.IsAtLeast(cephVolumeRawModeMinCephVersion) {
// When the block is encrypted we need to list against the encrypted device mapper
if !isEncrypted {
block = fmt.Sprintf("/mnt/%s", a.nodeName)
@@ -220,8 +226,10 @@ func (a *OsdAgent) initializeBlockPVC(context *clusterd.Context, devices *Device
var baseArgs []string
cephVolumeMode := "lvm"
if a.clusterInfo.CephVersion.IsAtLeast(cephVolumeRawModeMinCephVersion) && !lvBackedPV {
if a.clusterInfo.CephVersion.IsAtLeast(cephVolumeRawModeMinCephVersion) {
cephVolumeMode = "raw"
} else if lvBackedPV {
return "", "", "", errors.New("OSD on LV-backed PVC requires new Ceph to use raw mode")
}
// Create a specific log directory so that each prepare command will have its own log
@@ -278,20 +286,12 @@ func (a *OsdAgent) initializeBlockPVC(context *clusterd.Context, devices *Device
var err error
var deviceArg string
if lvBackedPV {
// pass 'vg/lv' to ceph-volume
deviceArg, err = sys.GetLVName(context.Executor, device.Config.Name)
if err != nil {
return "", "", "", errors.Wrapf(err, "failed to get lv name from device path %q", device.Config.Name)
}
} else {
deviceArg = device.Config.Name
logger.Info("devlink names:")
for _, devlink := range device.PersistentDevicePaths {
logger.Info(devlink)
if strings.HasPrefix(devlink, "/dev/mapper") {
deviceArg = devlink
}
deviceArg = device.Config.Name
logger.Info("devlink names:")
for _, devlink := range device.PersistentDevicePaths {
logger.Info(devlink)
if strings.HasPrefix(devlink, "/dev/mapper") {
deviceArg = devlink
}
}
@@ -335,7 +335,7 @@ func (a *OsdAgent) initializeBlockPVC(context *clusterd.Context, devices *Device
}
logger.Infof("%v", op)
// if raw mode is used or PV on LV, let's return the path of the device
if lvBackedPV || cephVolumeMode == "raw" && !isEncrypted {
if cephVolumeMode == "raw" && !isEncrypted {
blockPath = deviceArg
} else if cephVolumeMode == "raw" && isEncrypted {
blockPath = getEncryptedBlockPath(op, oposd.DmcryptBlockType)
+286
View File
@@ -17,6 +17,7 @@ limitations under the License.
package osd
import (
"fmt"
"io/ioutil"
"os"
"path"
@@ -269,6 +270,271 @@ var cephVolumeRAWTestResult = `{
}
`
func createPVCAvailableDevices() *DeviceOsdMapping {
devices := &DeviceOsdMapping{
Entries: map[string]*DeviceOsdIDEntry{
"data": {
Data: -1,
Metadata: nil,
Config: DesiredDevice{
Name: "/mnt/set1-data-0-rpf2k",
OSDsPerDevice: 1,
MetadataDevice: "",
DatabaseSizeMB: 0,
DeviceClass: "",
IsFilter: false,
IsDevicePathFilter: false,
},
PersistentDevicePaths: []string{
"/dev/rook-vg/rook-lv1",
"/dev/mapper/rook--vg-rook--lv1",
"/dev/disk/by-id/dm-name-rook--vg-rook--lv1",
"/dev/disk/by-id/dm-uuid-LVM-4BOeIsrVP5O2J36cVqMSJNLEcwGIrqSF12RyWdpUaiCuAqOa1hAmD6EUYTO6dyD1",
},
},
},
}
return devices
}
func TestConfigureCVDevices(t *testing.T) {
originalLVMConfPath := lvmConfPath
lvmConfPathTemp, err := ioutil.TempFile("", "lvmconf")
if err != nil {
t.Fatal(err)
}
lvmConfPath = lvmConfPathTemp.Name()
defer func() {
os.Remove(lvmConfPath)
lvmConfPath = originalLVMConfPath
}()
originalCephConfigDir := cephConfigDir
cephConfigDir, err = ioutil.TempDir("", "")
if err != nil {
t.Fatal(err)
}
defer func() {
os.RemoveAll(cephConfigDir)
cephConfigDir = originalCephConfigDir
}()
nodeName := "set1-data-0-rpf2k"
mountedDev := "/mnt/" + nodeName
mapperDev := "/dev/mapper/rook--vg-rook--lv1"
clusterFSID := "4bfe8b72-5e69-4330-b6c0-4d914db8ab89"
osdUUID := "c03d7353-96e5-4a41-98de-830dfff97d06"
lvBlockPath := "/dev/rook-vg/rook-lv1"
// Test case for creating new raw mode OSD on LV-backed PVC
{
executor := &exectest.MockExecutor{}
executor.MockExecuteCommandWithOutputFile = func(command string, outFileArg string, args ...string) (string, error) {
return "{\"key\":\"mysecurekey\"}", nil
}
executor.MockExecuteCommandWithOutput = func(command string, args ...string) (string, error) {
logger.Infof("[MockExecuteCommandWithOutput] %s %v", command, args)
if command == "lsblk" && args[0] == mountedDev {
return fmt.Sprintf(`SIZE="17179869184" ROTA="1" RO="0" TYPE="lvm" PKNAME="" NAME="%s" KNAME="/dev/dm-1, a ...interface{})`, mapperDev), nil
}
if args[1] == "ceph-volume" && args[4] == "lvm" && args[5] == "list" && args[6] == mapperDev {
return `{}`, nil
}
if args[1] == "ceph-volume" && args[4] == "raw" && args[5] == "list" && args[6] == mountedDev {
return fmt.Sprintf(`{
"0": {
"ceph_fsid": "%s",
"device": "%s",
"osd_id": 0,
"osd_uuid": "%s",
"type": "bluestore"
}
}
`, clusterFSID, mountedDev, osdUUID), nil
}
return "", errors.Errorf("unknown command %s %s", command, args)
}
executor.MockExecuteCommandWithCombinedOutput = func(command string, args ...string) (string, error) {
logger.Infof("[MockExecuteCommandWithCombinedOutput] %s %v", command, args)
if args[1] == "ceph-volume" && args[2] == "raw" && args[3] == "prepare" && args[4] == "--bluestore" && args[6] == mapperDev {
return initializeBlockPVCTestResult, nil
}
return "", errors.Errorf("unknown command %s %s", command, args)
}
context := &clusterd.Context{Executor: executor, ConfigDir: cephConfigDir}
clusterInfo := &cephclient.ClusterInfo{
CephVersion: cephver.CephVersion{Major: 14, Minor: 2, Extra: 8},
FSID: clusterFSID,
}
agent := &OsdAgent{clusterInfo: clusterInfo, nodeName: nodeName, pvcBacked: true}
devices := createPVCAvailableDevices()
deviceOSDs, err := agent.configureCVDevices(context, devices)
assert.Nil(t, err)
assert.Len(t, deviceOSDs, 1)
deviceOSD := deviceOSDs[0]
logger.Infof("deviceOSDs: %+v", deviceOSDs)
assert.Equal(t, osdUUID, deviceOSD.UUID)
assert.Equal(t, mountedDev, deviceOSD.BlockPath)
assert.Equal(t, true, deviceOSD.SkipLVRelease)
assert.Equal(t, true, deviceOSD.LVBackedPV)
assert.Equal(t, "raw", deviceOSD.CVMode)
assert.Equal(t, "bluestore", deviceOSD.Store)
}
{
// Test case for tending to create new lvm mode OSD on LV-backed PVC, but it catches an error
executor := &exectest.MockExecutor{}
executor.MockExecuteCommandWithOutput = func(command string, args ...string) (string, error) {
logger.Infof("[MockExecuteCommandWithOutput] %s %v", command, args)
if command == "lsblk" && args[0] == mountedDev {
return fmt.Sprintf(`SIZE="17179869184" ROTA="1" RO="0" TYPE="lvm" PKNAME="" NAME="%s" KNAME="/dev/dm-1, a ...interface{})`, mapperDev), nil
}
return "", errors.Errorf("unknown command %s %s", command, args)
}
executor.MockExecuteCommandWithCombinedOutput = func(command string, args ...string) (string, error) {
logger.Infof("[MockExecuteCommandWithCombinedOutput] %s %v", command, args)
return "", errors.Errorf("unknown command %s %s", command, args)
}
context := &clusterd.Context{Executor: executor, ConfigDir: cephConfigDir}
clusterInfo := &cephclient.ClusterInfo{
CephVersion: cephver.CephVersion{Major: 14, Minor: 2, Extra: 7}, // It doesn't support raw mode OSD
FSID: clusterFSID,
}
agent := &OsdAgent{clusterInfo: clusterInfo, nodeName: nodeName, pvcBacked: true}
devices := createPVCAvailableDevices()
_, err := agent.configureCVDevices(context, devices)
assert.EqualError(t, err, "failed to initialize devices on PVC: OSD on LV-backed PVC requires new Ceph to use raw mode")
}
{
// Test case for with no available lvm mode OSD and existing raw mode OSD on LV-backed PVC, it should return info of raw mode OSD
executor := &exectest.MockExecutor{}
executor.MockExecuteCommandWithOutput = func(command string, args ...string) (string, error) {
logger.Infof("[MockExecuteCommandWithOutput] %s %v", command, args)
if command == "lsblk" && args[0] == mountedDev {
return fmt.Sprintf(`SIZE="17179869184" ROTA="1" RO="0" TYPE="lvm" PKNAME="" NAME="%s" KNAME="/dev/dm-1, a ...interface{})`, mapperDev), nil
}
if args[1] == "ceph-volume" && args[4] == "lvm" && args[5] == "list" && args[6] == mapperDev {
return `{}`, nil
}
if args[1] == "ceph-volume" && args[4] == "raw" && args[5] == "list" && args[6] == mountedDev {
return fmt.Sprintf(`{
"0": {
"ceph_fsid": "%s",
"device": "%s",
"osd_id": 0,
"osd_uuid": "%s",
"type": "bluestore"
}
}
`, clusterFSID, mountedDev, osdUUID), nil
}
return "", errors.Errorf("unknown command %s %s", command, args)
}
executor.MockExecuteCommandWithCombinedOutput = func(command string, args ...string) (string, error) {
return "", errors.Errorf("unknown command %s %s", command, args)
}
context := &clusterd.Context{Executor: executor, ConfigDir: cephConfigDir}
clusterInfo := &cephclient.ClusterInfo{
CephVersion: cephver.CephVersion{Major: 14, Minor: 2, Extra: 8}, // It supports raw mode OSD
FSID: clusterFSID,
}
agent := &OsdAgent{clusterInfo: clusterInfo, nodeName: nodeName, pvcBacked: true}
devices := &DeviceOsdMapping{Entries: map[string]*DeviceOsdIDEntry{}}
deviceOSDs, err := agent.configureCVDevices(context, devices)
assert.Nil(t, err)
assert.Len(t, deviceOSDs, 1)
deviceOSD := deviceOSDs[0]
logger.Infof("deviceOSDs: %+v", deviceOSDs)
assert.Equal(t, osdUUID, deviceOSD.UUID)
assert.Equal(t, mountedDev, deviceOSD.BlockPath)
assert.Equal(t, true, deviceOSD.SkipLVRelease)
assert.Equal(t, true, deviceOSD.LVBackedPV)
assert.Equal(t, "raw", deviceOSD.CVMode)
assert.Equal(t, "bluestore", deviceOSD.Store)
}
{
// Test case for a lvm mode OSD on LV-backed PVC, it should return info of lvm mode OSD
executor := &exectest.MockExecutor{}
executor.MockExecuteCommandWithOutput = func(command string, args ...string) (string, error) {
logger.Infof("[MockExecuteCommandWithOutput] %s %v", command, args)
if command == "lsblk" && args[0] == mountedDev {
return fmt.Sprintf(`SIZE="17179869184" ROTA="1" RO="0" TYPE="lvm" PKNAME="" NAME="%s" KNAME="/dev/dm-1"
`, mapperDev), nil
}
if args[1] == "ceph-volume" && args[4] == "lvm" && args[5] == "list" {
return fmt.Sprintf(`{
"0": [
{
"devices": [
"/dev/sdb"
],
"lv_name": "lv1",
"lv_path": "%[1]s",
"lv_size": "6.00g",
"lv_tags": "ceph.block_device=%[1]s,ceph.block_uuid=hO8Hua-3H6B-qEt0-0NNN-ykFF-lsos-rSlmt2,ceph.cephx_lockbox_secret=,ceph.cluster_fsid=%[2]s,ceph.cluster_name=ceph,ceph.crush_device_class=None,ceph.encrypted=0,ceph.osd_fsid=%[3]s,ceph.osd_id=0,ceph.osdspec_affinity=,ceph.type=block,ceph.vdo=0",
"lv_uuid": "hO8Hua-3H6B-qEt0-0NNN-ykFF-lsos-rSlmt2",
"name": "lv1",
"path": "%[1]s",
"tags": {
"ceph.block_device": "%[1]s",
"ceph.block_uuid": "hO8Hua-3H6B-qEt0-0NNN-ykFF-lsos-rSlmt2",
"ceph.cephx_lockbox_secret": "",
"ceph.cluster_fsid": "%[2]s",
"ceph.cluster_name": "ceph",
"ceph.crush_device_class": "None",
"ceph.encrypted": "0",
"ceph.osd_fsid": "%[3]s",
"ceph.osd_id": "0",
"ceph.osdspec_affinity": "",
"ceph.type": "block",
"ceph.vdo": "0"
},
"type": "block",
"vg_name": "test-vg"
}
]
}
`, lvBlockPath, clusterFSID, osdUUID), nil
}
return "", errors.Errorf("unknown command %s %s", command, args)
}
context := &clusterd.Context{Executor: executor, ConfigDir: cephConfigDir}
clusterInfo := &cephclient.ClusterInfo{
CephVersion: cephver.CephVersion{Major: 14, Minor: 2, Extra: 8}, // It supports raw mode OSD
FSID: clusterFSID,
}
agent := &OsdAgent{clusterInfo: clusterInfo, nodeName: nodeName, pvcBacked: true}
devices := &DeviceOsdMapping{Entries: map[string]*DeviceOsdIDEntry{}}
deviceOSDs, err := agent.configureCVDevices(context, devices)
assert.Nil(t, err)
assert.Len(t, deviceOSDs, 1)
deviceOSD := deviceOSDs[0]
logger.Infof("deviceOSDs: %+v", deviceOSDs)
assert.Equal(t, osdUUID, deviceOSD.UUID)
assert.Equal(t, lvBlockPath, deviceOSD.BlockPath)
assert.Equal(t, true, deviceOSD.SkipLVRelease)
assert.Equal(t, true, deviceOSD.LVBackedPV)
assert.Equal(t, "lvm", deviceOSD.CVMode)
assert.Equal(t, "bluestore", deviceOSD.Store)
}
}
func TestInitializeBlockPVC(t *testing.T) {
executor := &exectest.MockExecutor{}
executor.MockExecuteCommandWithCombinedOutput = func(command string, args ...string) (string, error) {
@@ -354,6 +620,26 @@ func TestInitializeBlockPVC(t *testing.T) {
_, _, _, err = a.initializeBlockPVC(context, devices, false)
assert.NotNil(t, err)
// Test for OSD on LV-backed PVC where Ceph does not support raw mode.
// Expect no commands to be used.
executor.MockExecuteCommandWithCombinedOutput = func(command string, args ...string) (string, error) {
logger.Infof("%s %v", command, args)
return "", errors.Errorf("unknown command %s %s", command, args)
}
// Test with CephVersion{Major: 14, Minor: 2, Extra: 7}
clusterInfo = &cephclient.ClusterInfo{
CephVersion: cephver.CephVersion{Major: 14, Minor: 2, Extra: 7},
}
a = &OsdAgent{clusterInfo: clusterInfo, nodeName: "node1"}
devices = &DeviceOsdMapping{
Entries: map[string]*DeviceOsdIDEntry{
"data": {Data: -1, Metadata: nil, Config: DesiredDevice{Name: "/mnt/set1-data-0-rpf2k"}},
},
}
_, _, _, err = a.initializeBlockPVC(context, devices, true)
assert.NotNil(t, err)
logger.Infof("error message %v", err)
executor.MockExecuteCommandWithCombinedOutput = func(command string, args ...string) (string, error) {
logger.Infof("%s %v", command, args)
if args[1] == "ceph-volume" && args[2] == "raw" && args[3] == "prepare" && args[4] == "--bluestore" && args[7] == "--crush-device-class" {